# Create addon Source: https://docs.flexprice.io/api-reference/addons/create-addon /api-reference/openapi.json post /addons Use when defining an optional purchasable item (e.g. extra storage or support tier). Ideal for add-ons that customers can attach to a subscription. # Delete addon Source: https://docs.flexprice.io/api-reference/addons/delete-addon /api-reference/openapi.json delete /addons/{id} Use when retiring an addon (e.g. end-of-life). Returns 200 with success message. # Get addon Source: https://docs.flexprice.io/api-reference/addons/get-addon /api-reference/openapi.json get /addons/{id} Use when you need to load a single addon (e.g. for display or to attach to a subscription). # Get addon by lookup key Source: https://docs.flexprice.io/api-reference/addons/get-addon-by-lookup-key /api-reference/openapi.json get /addons/lookup/{lookup_key} Use when resolving an addon by external id (e.g. from your product catalog). Ideal for integrations. # Query addons Source: https://docs.flexprice.io/api-reference/addons/query-addons /api-reference/openapi.json post /addons/search Use when listing or searching addons (e.g. catalog or subscription builder). Returns a paginated list; supports filtering and sorting. # Update addon Source: https://docs.flexprice.io/api-reference/addons/update-addon /api-reference/openapi.json put /addons/{id} Use when changing addon details (e.g. name, pricing, or metadata). # Query alert logs Source: https://docs.flexprice.io/api-reference/alerts/query-alert-logs /api-reference/openapi.json post /alerts/search Use when viewing or searching alert history (e.g. support triage or customer-facing alert log). Returns a paginated list; supports filtering by type, customer, subscription. # Create checkout session Source: https://docs.flexprice.io/api-reference/checkout/create-checkout-session /api-reference/openapi.json post /checkout/sessions # Delete checkout session Source: https://docs.flexprice.io/api-reference/checkout/delete-checkout-session /api-reference/openapi.json delete /checkout/sessions/{id} # Get checkout session Source: https://docs.flexprice.io/api-reference/checkout/get-checkout-session /api-reference/openapi.json get /checkout/sessions/{id} # Create costsheet Source: https://docs.flexprice.io/api-reference/costs/create-costsheet /api-reference/openapi.json post /costs Use when setting up a new pricing configuration (e.g. a new product or region). Costsheets group prices and define the default for the environment. # Delete costsheet Source: https://docs.flexprice.io/api-reference/costs/delete-costsheet /api-reference/openapi.json delete /costs/{id} Use when retiring a costsheet (e.g. end-of-life product). Soft-deletes; status set to deleted. # Get active costsheet Source: https://docs.flexprice.io/api-reference/costs/get-active-costsheet /api-reference/openapi.json get /costs/active Use when you need the tenant's default pricing configuration (e.g. for checkout or plan display). Returns the active costsheet for the environment. # Get combined revenue and cost analytics Source: https://docs.flexprice.io/api-reference/costs/get-combined-revenue-and-cost-analytics /api-reference/openapi.json post /costs/analytics Use when building dashboards or reports that need revenue vs cost, ROI, and margin over a time period (e.g. finance views or executive summaries). # Get combined revenue and cost analytics (V2) Source: https://docs.flexprice.io/api-reference/costs/get-combined-revenue-and-cost-analytics-v2 /api-reference/openapi.json post /costs/analytics-v2 Use when you need the same revenue/cost/ROI analytics but computed from the costsheet usage-tracking pipeline (e.g. for consistency with usage-based cost data). # Get costsheet Source: https://docs.flexprice.io/api-reference/costs/get-costsheet /api-reference/openapi.json get /costs/{id} Use when you need to load a single costsheet (e.g. for editing or display). Supports optional expand for related prices. # Query costsheets Source: https://docs.flexprice.io/api-reference/costs/query-costsheets /api-reference/openapi.json post /costs/search Use when listing or searching costsheets (e.g. admin catalog). Returns a paginated list; supports filtering and sorting. # Update costsheet Source: https://docs.flexprice.io/api-reference/costs/update-costsheet /api-reference/openapi.json put /costs/{id} Use when changing costsheet name or metadata. # Get coupon association Source: https://docs.flexprice.io/api-reference/coupon-associations/get-coupon-association /api-reference/openapi.json get /coupons/associations/{id} Get a single coupon association by ID. Coupon associations are created and removed via the subscription modify API. # List coupon associations Source: https://docs.flexprice.io/api-reference/coupon-associations/list-coupon-associations /api-reference/openapi.json get /coupons/associations List coupon associations with optional filters. Coupon associations are created and removed via the subscription modify API. # Create coupon Source: https://docs.flexprice.io/api-reference/coupons/create-coupon /api-reference/openapi.json post /coupons Use when creating a discount (e.g. promo code or referral). Ideal for percent or fixed value, with optional validity and usage limits. # Delete coupon Source: https://docs.flexprice.io/api-reference/coupons/delete-coupon /api-reference/openapi.json delete /coupons/{id} Use when retiring a coupon (e.g. campaign ended). Returns 200 with success message. # Get coupon Source: https://docs.flexprice.io/api-reference/coupons/get-coupon /api-reference/openapi.json get /coupons/{id} Use when you need to load a single coupon (e.g. for display or to validate a code). # Get coupon by code Source: https://docs.flexprice.io/api-reference/coupons/get-coupon-by-code /api-reference/openapi.json get /coupons/code/{code} Use when resolving a coupon by promo code (e.g. checkout or validation). # Query coupons Source: https://docs.flexprice.io/api-reference/coupons/query-coupons /api-reference/openapi.json post /coupons/search Use when listing or searching coupons (e.g. promo management). Returns a paginated list; supports filtering and sorting. # Update coupon Source: https://docs.flexprice.io/api-reference/coupons/update-coupon /api-reference/openapi.json put /coupons/{id} Use when changing coupon config (e.g. value, validity, or usage limits). # Create credit grant Source: https://docs.flexprice.io/api-reference/credit-grants/create-credit-grant /api-reference/openapi.json post /creditgrants Use when giving a customer or plan credits (e.g. prepaid balance or promotional credits). Scope can be plan or subscription; supports start/end dates. # Delete credit grant Source: https://docs.flexprice.io/api-reference/credit-grants/delete-credit-grant /api-reference/openapi.json delete /creditgrants/{id} Use when removing or ending a credit grant (e.g. revoke promo or close prepaid). Plan-scoped grants are archived; subscription-scoped supports optional effective_date in body. # Get credit grant Source: https://docs.flexprice.io/api-reference/credit-grants/get-credit-grant /api-reference/openapi.json get /creditgrants/{id} Use when you need to load a single credit grant (e.g. for display or to check balance). # Get plan credit grants Source: https://docs.flexprice.io/api-reference/credit-grants/get-plan-credit-grants /api-reference/openapi.json get /plans/{id}/creditgrants Use when listing credits attached to a plan (e.g. included prepaid or promo credits). # Update credit grant Source: https://docs.flexprice.io/api-reference/credit-grants/update-credit-grant /api-reference/openapi.json put /creditgrants/{id} Use when changing a credit grant (e.g. amount or end date). Request body contains the fields to update. # Create credit note Source: https://docs.flexprice.io/api-reference/credit-notes/create-credit-note /api-reference/openapi.json post /creditnotes Use when issuing a refund or adjustment (e.g. customer dispute or proration). Links to an invoice; create as draft then finalize. # Finalize credit note Source: https://docs.flexprice.io/api-reference/credit-notes/finalize-credit-note /api-reference/openapi.json post /creditnotes/{id}/finalize Use when locking a draft credit note and applying the credit (e.g. after approval). Once finalized, applied per billing provider. # Get credit note Source: https://docs.flexprice.io/api-reference/credit-notes/get-credit-note /api-reference/openapi.json get /creditnotes/{id} Use when you need to load a single credit note (e.g. for display or reconciliation). # Void credit note Source: https://docs.flexprice.io/api-reference/credit-notes/void-credit-note /api-reference/openapi.json post /creditnotes/{id}/void Use when cancelling a draft credit note (e.g. created by mistake). Only draft credit notes can be voided. # Create customer Source: https://docs.flexprice.io/api-reference/customers/create-customer /api-reference/openapi.json post /customers Use when onboarding a new billing customer (e.g. sign-up or CRM sync). Ideal for linking via external_customer_id to your app's user id. # Delete customer Source: https://docs.flexprice.io/api-reference/customers/delete-customer /api-reference/openapi.json delete /customers/{id} Use when removing a customer (e.g. GDPR or churn). Returns 204 No Content on success. # Get customer Source: https://docs.flexprice.io/api-reference/customers/get-customer /api-reference/openapi.json get /customers/{id} Use when you need to load a single customer (e.g. for a billing portal or to attach a subscription). # Get customer by external ID Source: https://docs.flexprice.io/api-reference/customers/get-customer-by-external-id /api-reference/openapi.json get /customers/external/{external_id} Use when resolving a customer by your app's id (e.g. from your user table). Ideal for integrations that key by external id. # Get customer entitlements Source: https://docs.flexprice.io/api-reference/customers/get-customer-entitlements /api-reference/openapi.json get /customers/{id}/entitlements Use when checking what a customer can access (e.g. feature gating or usage limits). Supports optional filters (feature_ids, subscription_ids). # Get customer entitlements by external ID Source: https://docs.flexprice.io/api-reference/customers/get-customer-entitlements-by-external-id /api-reference/openapi.json get /customers/external/{external_id}/entitlements Use when checking entitlements by your app's customer id (e.g. feature gating at the edge). Supports optional filters (feature_ids, subscription_ids). # Get customer usage summary Source: https://docs.flexprice.io/api-reference/customers/get-customer-usage-summary /api-reference/openapi.json get /customers/usage Use when showing a customer's usage (e.g. portal or overage alerts). Identify by customer_id or customer_lookup_key; supports filters. # Get upcoming credit grant applications Source: https://docs.flexprice.io/api-reference/customers/get-upcoming-credit-grant-applications /api-reference/openapi.json get /customers/{id}/grants/upcoming Use when showing upcoming or pending credits for a customer (e.g. in a portal or for forecasting). # Query customers Source: https://docs.flexprice.io/api-reference/customers/query-customers /api-reference/openapi.json post /customers/search Use when listing or searching customers (e.g. admin CRM or reporting). Returns a paginated list; supports filtering and sorting. # Update customer Source: https://docs.flexprice.io/api-reference/customers/update-customer /api-reference/openapi.json put /customers Use when updating customer details (e.g. name, email, or metadata). Identify by id or external_customer_id. # Create entitlement Source: https://docs.flexprice.io/api-reference/entitlements/create-entitlement /api-reference/openapi.json post /entitlements Use when attaching a feature (and its limit) to a plan or addon (e.g. "10 seats" or "1000 API calls"). Defines what the plan/addon includes. # Create entitlements in bulk Source: https://docs.flexprice.io/api-reference/entitlements/create-entitlements-in-bulk /api-reference/openapi.json post /entitlements/bulk Use when attaching many features to a plan or addon at once (e.g. initial plan setup or import). Bulk version of create entitlement. # Delete entitlement Source: https://docs.flexprice.io/api-reference/entitlements/delete-entitlement /api-reference/openapi.json delete /entitlements/{id} Use when removing a feature from a plan or addon (e.g. deprecating a capability). Returns 200 with success message. # Get addon entitlements Source: https://docs.flexprice.io/api-reference/entitlements/get-addon-entitlements /api-reference/openapi.json get /addons/{id}/entitlements Use when checking what features or limits an addon grants (e.g. for display or entitlement logic). # Get entitlement Source: https://docs.flexprice.io/api-reference/entitlements/get-entitlement /api-reference/openapi.json get /entitlements/{id} Use when you need to load a single entitlement (e.g. to display or edit a feature limit). # Get plan entitlements Source: https://docs.flexprice.io/api-reference/entitlements/get-plan-entitlements /api-reference/openapi.json get /plans/{id}/entitlements Use when checking what a plan includes (e.g. feature list or limits for display or gating). # Query entitlements Source: https://docs.flexprice.io/api-reference/entitlements/query-entitlements /api-reference/openapi.json post /entitlements/search Use when listing or searching entitlements (e.g. plan editor or audit). Returns a paginated list; supports filtering by plan, addon, feature. # Update entitlement Source: https://docs.flexprice.io/api-reference/entitlements/update-entitlement /api-reference/openapi.json put /entitlements/{id} Use when changing an entitlement (e.g. increasing or decreasing a limit). Request body contains the fields to update. # Clone an environment Source: https://docs.flexprice.io/api-reference/environments/clone-an-environment /api-reference/openapi.json post /environments/{id}/clone Clone all published features and plans from the source environment into a target environment. If target_environment_id is provided, entities are cloned into that existing environment. Otherwise a new environment is created from name and type first. # Error responses Source: https://docs.flexprice.io/api-reference/error-responses Understand Flexprice API error response formats and error codes # Error responses All Flexprice API errors follow a consistent structure. Each error response includes: * `success`: Boolean flag indicating whether the request was successful (always `false` for errors) * `error`: Object containing error details with: * `message`: Human-readable description of the error * `internal_error`: Internal error details (may be empty for client errors) Error responses may also include additional fields specific to the error type. ```json theme={null} theme={null} { "success": false, "error": { "message": "Could not find a resource of type Plan with identifier: DBhwNcMNfYgeBpn.", "internal_error": "" } } ``` ## Common error types The table below lists common error types. When processing errors programmatically, use the HTTP status code along with the error message. | Title | Status code | Description | | --------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Authentication failure | 401 | Your request did not provide a valid or well-formatted API key. Ensure you're including the `x-api-key` header with a valid API key. | | Request validation error | 400 | A request that fails validation will return human-readable information about what went wrong. The error response includes a `message` field describing the validation issues. | | Duplicate resource creation | 400 | Returned when attempting to create a resource that already exists. | | Resource not found | 404 | This is returned when a provided identifier does not map to a resource that is accessible by the current account. | | Feature not available | 404 | Returned when the endpoint or feature is behind a feature flag on the server-side and is unavailable for current use. | | URL not found | 404 | Returned when the requested URL was not found on the server. | | Resource conflict | 409 | Returned when the request conflicts with another concurrent request. This may occur when multiple requests attempt to modify the same resource simultaneously (e.g., the credits ledger). You can safely retry these requests. | | Too many requests | 429 | Returned when the request rate exceeds the limit. | | Internal Server error | 500 | This is an internal server error. The Flexprice team has been notified! | # Bulk ingest events Source: https://docs.flexprice.io/api-reference/events/bulk-ingest-events /api-reference/openapi.json post /events/bulk Use when batching usage events (e.g. backfill or high-volume ingestion). More efficient than single event calls; returns 202 when accepted. # Get event Source: https://docs.flexprice.io/api-reference/events/get-event /api-reference/openapi.json get /events/{id} Use when debugging a specific event (e.g. why it failed or how it was aggregated). Includes processing status and debug info. # Get Hugging Face inference data Source: https://docs.flexprice.io/api-reference/events/get-hugging-face-inference-data /api-reference/openapi.json post /events/huggingface-inference Use when fetching Hugging Face inference usage or billing data (e.g. for HF-specific reporting or reconciliation). # Get usage analytics Source: https://docs.flexprice.io/api-reference/events/get-usage-analytics /api-reference/openapi.json post /events/analytics Use when building analytics views (e.g. usage by feature or customer over time). Supports filtering, grouping, and time-series breakdown. # Get usage by meter Source: https://docs.flexprice.io/api-reference/events/get-usage-by-meter /api-reference/openapi.json post /events/usage/meter Use when showing usage for a specific meter (e.g. dashboard or overage check). Supports time range, filters, and grouping by customer or subscription. # Get usage statistics Source: https://docs.flexprice.io/api-reference/events/get-usage-statistics /api-reference/openapi.json post /events/usage Use when building usage reports or dashboards across events. Supports filters and grouping; defaults to last 7 days if no range provided. # Ingest event Source: https://docs.flexprice.io/api-reference/events/ingest-event /api-reference/openapi.json post /events Use when sending a single usage event from your app (e.g. one API call or one GB stored). Events are processed asynchronously; returns 202 with event_id. # List raw events Source: https://docs.flexprice.io/api-reference/events/list-raw-events /api-reference/openapi.json post /events/query Use when debugging ingestion or exporting raw event data (e.g. support or audit). Returns a paginated list; supports time range and sorting. # Clone a feature Source: https://docs.flexprice.io/api-reference/features/clone-a-feature /api-reference/openapi.json post /features/{id}/clone Clone an existing feature # Create feature Source: https://docs.flexprice.io/api-reference/features/create-feature /api-reference/openapi.json post /features Use when defining a new feature or capability to gate or meter (e.g. feature flags or usage-based limits). Ideal for boolean or usage features. # Delete feature Source: https://docs.flexprice.io/api-reference/features/delete-feature /api-reference/openapi.json delete /features/{id} Use when retiring a feature (e.g. deprecated capability). Returns 200 with success message. # Query features Source: https://docs.flexprice.io/api-reference/features/query-features /api-reference/openapi.json post /features/search Use when listing or searching features (e.g. catalog or entitlement setup). Returns a paginated list; supports filtering and sorting. # Update feature Source: https://docs.flexprice.io/api-reference/features/update-feature /api-reference/openapi.json put /features/{id} Use when changing feature definition (e.g. name, type, or meter). Request body contains the fields to update. # Create group Source: https://docs.flexprice.io/api-reference/groups/create-group /api-reference/openapi.json post /groups Use when organizing entities into a group (e.g. for filtering prices or plans by product line or region). # Delete group Source: https://docs.flexprice.io/api-reference/groups/delete-group /api-reference/openapi.json delete /groups/{id} Use when removing a group and clearing its entity associations (e.g. retiring a product line). Returns 204 or 200 on success. # Get group Source: https://docs.flexprice.io/api-reference/groups/get-group /api-reference/openapi.json get /groups/{id} Use when you need to load a single group (e.g. for display or to assign entities). # Query groups Source: https://docs.flexprice.io/api-reference/groups/query-groups /api-reference/openapi.json post /groups/search Use when listing or searching groups (e.g. admin catalog). Returns a paginated list; supports filtering and sorting. # Delink integration mapping Source: https://docs.flexprice.io/api-reference/integrations/delink-integration-mapping /api-reference/openapi.json delete /integrations/link Soft-delete (archive) the mapping between a FlexPrice entity and a provider entity. # Get entity integration mappings Source: https://docs.flexprice.io/api-reference/integrations/get-entity-integration-mappings /api-reference/openapi.json get /integrations/mappings Get integration mappings for a specific entity by entity type and entity ID. # Get integration configurations Source: https://docs.flexprice.io/api-reference/integrations/get-integration-configurations /api-reference/openapi.json get /integrations/config Returns the base capabilities and current sync configuration for all connected providers. # Link integration mapping Source: https://docs.flexprice.io/api-reference/integrations/link-integration-mapping /api-reference/openapi.json post /integrations/link Link a FlexPrice entity to provider entity with provider-specific side effects. # Introduction Source: https://docs.flexprice.io/api-reference/introduction # Welcome to the Flexprice API The Flexprice API lets you programmatically manage pricing, subscriptions, and billing for your SaaS application. ## Base URL All API requests must use one of the following base URLs: * **US Region**: `https://us.api.flexprice.io/v1` * **India Region**: `https://api.cloud.flexprice.io/v1` Select your preferred region using the server selector in the API playground: Region Selector ## Authentication All API endpoints require authentication using an API key. Include your API key in the `x-api-key` header with every request. ```bash theme={null} x-api-key: ``` Create API keys in the Flexprice Dashboard. If you need help, email us at [support@flexprice.io](mailto:support@flexprice.io). ## Response Format Successful API responses return JSON data directly. The response structure varies by endpoint—see individual endpoint documentation for details. ## Error Handling The API uses standard HTTP status codes: * `200` - Success * `201` - Created * `400` - Bad Request * `401` - Unauthorized * `404` - Not Found * `409` - Conflict * `429` - Too Many Requests * `500` - Internal Server Error For detailed error response formats and error codes, see the [Error Responses](/api-reference/error-responses) documentation. ## Need Help? * Email: [support@flexprice.io](mailto:support@flexprice.io) * Documentation Updates: [tech@flexprice.io](mailto:tech@flexprice.io) # Attempt invoice payment Source: https://docs.flexprice.io/api-reference/invoices/attempt-invoice-payment /api-reference/openapi.json post /invoices/{id}/payment/attempt Use when paying an invoice with the customer's wallet balance (e.g. prepaid credits or balance applied at checkout). # Create one-off invoice Source: https://docs.flexprice.io/api-reference/invoices/create-one-off-invoice /api-reference/openapi.json post /invoices Use when creating a manual or one-off invoice (e.g. custom charge or non-recurring billing). Invoice is created in draft; finalize when ready. # Finalize invoice Source: https://docs.flexprice.io/api-reference/invoices/finalize-invoice /api-reference/openapi.json post /invoices/{id}/finalize Use when locking an invoice for payment (e.g. after review). Once finalized, line items are locked; invoice can be paid or voided. # Get customer invoice summary Source: https://docs.flexprice.io/api-reference/invoices/get-customer-invoice-summary /api-reference/openapi.json get /customers/{id}/invoices/summary Use when showing a customer's invoice overview (e.g. billing portal or balance summary). Includes totals and multi-currency breakdown. # Get invoice Source: https://docs.flexprice.io/api-reference/invoices/get-invoice /api-reference/openapi.json get /invoices/{id} Use when loading an invoice for display or editing (e.g. portal or reconciliation). Supports group_by for usage breakdown and force_runtime_recalculation. # Get invoice PDF Source: https://docs.flexprice.io/api-reference/invoices/get-invoice-pdf /api-reference/openapi.json get /invoices/{id}/pdf Use when delivering an invoice PDF to the customer (e.g. email attachment or download). Use url=true for a presigned URL instead of binary. Use force_generate=true to regenerate and re-upload the PDF even if one already exists in S3. # Get invoice preview Source: https://docs.flexprice.io/api-reference/invoices/get-invoice-preview /api-reference/openapi.json post /invoices/preview Use when showing a customer what they will be charged (e.g. preview before checkout or plan change). No invoice is created. # Query invoices Source: https://docs.flexprice.io/api-reference/invoices/query-invoices /api-reference/openapi.json post /invoices/search Use when listing or searching invoices (e.g. admin view or customer history). Returns a paginated list; supports filtering by customer, status, date range. # Recalculate draft invoice (v2) Source: https://docs.flexprice.io/api-reference/invoices/recalculate-draft-invoice-v2 /api-reference/openapi.json post /invoices/{id}/recalculate-v2 Recalculates a draft SUBSCRIPTION invoice in-place (replaces line items, reapplies credits/coupons/taxes). Use when subscription or usage data changed before finalizing. # Recalculate invoice (voided invoice) Source: https://docs.flexprice.io/api-reference/invoices/recalculate-invoice-voided-invoice /api-reference/openapi.json post /invoices/{id}/recalculate Starts an async workflow that creates a fresh replacement invoice for a voided SUBSCRIPTION invoice (same billing period). Returns workflow_id and run_id; poll workflow status or GET the new invoice via recalculated_invoice_id after completion. # Trigger invoice communication webhook Source: https://docs.flexprice.io/api-reference/invoices/trigger-invoice-communication-webhook /api-reference/openapi.json post /invoices/{id}/comms/trigger Use when sending an invoice to the customer (e.g. trigger email or Slack). Payload includes full invoice details for your integration. # Update invoice Source: https://docs.flexprice.io/api-reference/invoices/update-invoice /api-reference/openapi.json put /invoices/{id} Use when updating invoice metadata or due date (e.g. PDF URL, net terms). For paid invoices only safe fields can be updated. # Update invoice payment status Source: https://docs.flexprice.io/api-reference/invoices/update-invoice-payment-status /api-reference/openapi.json put /invoices/{id}/payment Use when reconciling payment status from an external gateway or manual entry (e.g. mark paid after bank confirmation). # Void invoice Source: https://docs.flexprice.io/api-reference/invoices/void-invoice /api-reference/openapi.json post /invoices/{id}/void Use when cancelling an invoice (e.g. order cancelled or duplicate). Only unpaid invoices can be voided. # Pagination Source: https://docs.flexprice.io/api-reference/pagination Learn how to paginate through API responses using offset-based pagination # Pagination The Flexprice API uses offset-based pagination for endpoints that list resources, such as the [List Customers](/api-reference/customers/query-customers) endpoint. This lets you load resources incrementally without transferring large amounts of data in a single request. Use the `limit` and `offset` query parameters to control how many items are returned and where to start in the result set. For the first request, you typically use `offset=0`. The API then returns an updated `offset` in the response that you can reuse directly for the next page. ## Paginated API responses List endpoints return a standardized format with the requested items in an `items` array and pagination metadata in a `pagination` object. The response format is: ```json theme={null} theme={null} { "items": [ ... ], "pagination": { "limit": 50, "offset": 50, "total": 150 } } ``` **Response fields:** * `limit`: Maximum Number of items returned in this page (default: 50). * `offset`: The offset you should use when requesting the **next** page. For the first request, you typically pass `offset=0`; the API returns `offset = request_offset + limit` in the response. * `total`: Total number of items in the database that match your query, not just the items in the current response. **Fetching the next page:** To get the next page, reuse the `offset` value returned in the previous response instead of computing it yourself. For example: * First request: `limit=50&offset=0` returns items 1-50 and a `pagination.offset` value of `50`. * Second request: `limit=50&offset=50` (using the previous `pagination.offset`) returns items 51-100 and a `pagination.offset` value of `100`. * Third request: `limit=50&offset=100` returns items 101-150, and so on. **Sorting:** By default, resources are sorted by `created_at` in descending order (newest first). Use the `sort` and `order` query parameters to change the sorting behavior. ## Limits The default page size is 50 items. You can request up to 1000 items per page using the `limit` query parameter. # Create payment Source: https://docs.flexprice.io/api-reference/payments/create-payment /api-reference/openapi.json post /payments Use when recording a payment against an invoice (e.g. after receiving funds via a gateway or manual entry). # Delete payment Source: https://docs.flexprice.io/api-reference/payments/delete-payment /api-reference/openapi.json delete /payments/{id} Use when removing or voiding a payment record (e.g. correcting erroneous entries). Returns 200 with success message. # Get payment Source: https://docs.flexprice.io/api-reference/payments/get-payment /api-reference/openapi.json get /payments/{id} Use when you need to load a single payment (e.g. for a receipt view or reconciliation). # List payments Source: https://docs.flexprice.io/api-reference/payments/list-payments /api-reference/openapi.json get /payments Use when listing or searching payments (e.g. reconciliation UI or customer payment history). Returns a paginated list; supports filtering by customer, invoice, status. # Process payment Source: https://docs.flexprice.io/api-reference/payments/process-payment /api-reference/openapi.json post /payments/{id}/process Use when you need to charge or process a payment (e.g. trigger the payment provider to capture funds). Returns updated payment with status. # Update payment Source: https://docs.flexprice.io/api-reference/payments/update-payment /api-reference/openapi.json put /payments/{id} Use when updating payment status or metadata (e.g. after reconciliation or adding a reference). # Clone a plan Source: https://docs.flexprice.io/api-reference/plans/clone-a-plan /api-reference/openapi.json post /plans/{id}/clone Clone an existing plan, copying its active prices, published entitlements, and published credit grants # Create plan Source: https://docs.flexprice.io/api-reference/plans/create-plan /api-reference/openapi.json post /plans Use when defining a new pricing plan (e.g. Free, Pro, Enterprise). Attach prices and entitlements; customers subscribe to plans. # Delete plan Source: https://docs.flexprice.io/api-reference/plans/delete-plan /api-reference/openapi.json delete /plans/{id} Use when retiring a plan (e.g. end-of-life). Existing subscriptions may be affected. Returns 200 with success message. # Get plan Source: https://docs.flexprice.io/api-reference/plans/get-plan /api-reference/openapi.json get /plans/{id} Use when you need to load a single plan (e.g. for display or to create a subscription). # Query plans Source: https://docs.flexprice.io/api-reference/plans/query-plans /api-reference/openapi.json post /plans/search Use when listing or searching plans (e.g. plan picker or admin catalog). Returns a paginated list; supports filtering and sorting. # Synchronize plan prices Source: https://docs.flexprice.io/api-reference/plans/synchronize-plan-prices /api-reference/openapi.json post /plans/{id}/sync/subscriptions Use when you have changed plan prices and need to push them to all active subscriptions (e.g. global price update). Returns workflow ID. # Update plan Source: https://docs.flexprice.io/api-reference/plans/update-plan /api-reference/openapi.json put /plans/{id} Use when changing plan details (e.g. name, interval, or metadata). Partial update supported. # Create price unit Source: https://docs.flexprice.io/api-reference/price-units/create-price-unit /api-reference/openapi.json post /prices/units Use when defining a new unit of measure for pricing (e.g. GB, API call, seat). Ideal for metered or usage-based prices. # Delete price unit Source: https://docs.flexprice.io/api-reference/price-units/delete-price-unit /api-reference/openapi.json delete /prices/units/{id} Use when removing a price unit that is no longer needed. Fails if any price references this unit. # Get price unit Source: https://docs.flexprice.io/api-reference/price-units/get-price-unit /api-reference/openapi.json get /prices/units/{id} Use when you need to load a single price unit (e.g. for display or when creating a price). # Get price unit by code Source: https://docs.flexprice.io/api-reference/price-units/get-price-unit-by-code /api-reference/openapi.json get /prices/units/code/{code} Use when resolving a price unit by code (e.g. from an external catalog or config). Ideal for integrations. # List price units Source: https://docs.flexprice.io/api-reference/price-units/list-price-units /api-reference/openapi.json get /prices/units Use when listing price units (e.g. in a catalog or when creating prices). Returns a paginated list; supports status, sort, and pagination. # Query price units Source: https://docs.flexprice.io/api-reference/price-units/query-price-units /api-reference/openapi.json post /prices/units/search Use when searching or listing price units (e.g. admin catalog). Returns a paginated list; supports filtering and sorting. # Update price unit Source: https://docs.flexprice.io/api-reference/price-units/update-price-unit /api-reference/openapi.json put /prices/units/{id} Use when renaming or updating metadata for a price unit. Code is immutable once created. # Create price Source: https://docs.flexprice.io/api-reference/prices/create-price /api-reference/openapi.json post /prices Use when adding a new price to a plan or catalog (e.g. per-seat, flat, or metered). Ideal for both simple and usage-based pricing. # Create prices in bulk Source: https://docs.flexprice.io/api-reference/prices/create-prices-in-bulk /api-reference/openapi.json post /prices/bulk Use when creating many prices at once (e.g. importing a catalog or setting up a plan with multiple tiers). # Delete price Source: https://docs.flexprice.io/api-reference/prices/delete-price /api-reference/openapi.json delete /prices/{id} Use when retiring a price (e.g. end-of-life or replacement). Optional effective date or cascade for subscriptions. # Get price Source: https://docs.flexprice.io/api-reference/prices/get-price /api-reference/openapi.json get /prices/{id} Use when you need to load a single price (e.g. for display or editing). Response includes expanded meter and price unit when applicable. # Get price by lookup key Source: https://docs.flexprice.io/api-reference/prices/get-price-by-lookup-key /api-reference/openapi.json get /prices/lookup/{lookup_key} Use when resolving a price by external id (e.g. from your catalog or CMS). Ideal for integrations. # Query prices Source: https://docs.flexprice.io/api-reference/prices/query-prices /api-reference/openapi.json post /prices/search Use when listing or searching prices (e.g. plan builder or catalog). Returns a paginated list; supports filtering and sorting. # Update price Source: https://docs.flexprice.io/api-reference/prices/update-price /api-reference/openapi.json put /prices/{id} Use when changing price configuration (e.g. amount, billing scheme, or metadata). # Get a specific RBAC role Source: https://docs.flexprice.io/api-reference/rbac/get-a-specific-rbac-role /api-reference/openapi.json get /rbac/roles/{id} Use when you need to show or edit a single role (e.g. role detail page). Includes permissions, name, and description. # List all RBAC roles Source: https://docs.flexprice.io/api-reference/rbac/list-all-rbac-roles /api-reference/openapi.json get /rbac/roles Use when building role pickers or permission UIs. Returns all roles with permissions and descriptions. # Create scheduled task Source: https://docs.flexprice.io/api-reference/scheduled-tasks/create-scheduled-task /api-reference/openapi.json post /tasks/scheduled Use when setting up recurring data exports or other scheduled jobs. Ideal for report generation or syncing data on a schedule. # Delete a scheduled task Source: https://docs.flexprice.io/api-reference/scheduled-tasks/delete-a-scheduled-task /api-reference/openapi.json delete /tasks/scheduled/{id} Use when removing a scheduled task from the active roster. Archives the task and removes it from the scheduler (soft delete). # Get scheduled task Source: https://docs.flexprice.io/api-reference/scheduled-tasks/get-scheduled-task /api-reference/openapi.json get /tasks/scheduled/{id} Use when you need to load a single scheduled task (e.g. to show details in a UI or check its configuration). # List scheduled tasks Source: https://docs.flexprice.io/api-reference/scheduled-tasks/list-scheduled-tasks /api-reference/openapi.json get /tasks/scheduled Use when listing or managing scheduled tasks in an admin UI. Returns a list; supports filtering by status, type, and pagination. # Schedule draft finalization Source: https://docs.flexprice.io/api-reference/scheduled-tasks/schedule-draft-finalization /api-reference/openapi.json post /tasks/scheduled/schedule-draft-finalization Triggers the draft invoice finalization workflow that scans computed draft invoices whose finalization delay has elapsed and finalizes them (assign invoice number, sync to vendors, attempt payment). # Schedule update billing period Source: https://docs.flexprice.io/api-reference/scheduled-tasks/schedule-update-billing-period /api-reference/openapi.json post /tasks/scheduled/schedule-update-billing-period Use when you need to trigger a billing-period update workflow (e.g. to recalculate or sync billing windows). # Trigger force run Source: https://docs.flexprice.io/api-reference/scheduled-tasks/trigger-force-run /api-reference/openapi.json post /tasks/scheduled/{id}/run Use when you need to run a scheduled export immediately (e.g. on-demand report or catch-up). Supports optional custom time range. # Update a scheduled task Source: https://docs.flexprice.io/api-reference/scheduled-tasks/update-a-scheduled-task /api-reference/openapi.json put /tasks/scheduled/{id} Use when pausing or resuming a scheduled task. Only the enabled field can be changed. # Create a new API key Source: https://docs.flexprice.io/api-reference/secrets/create-a-new-api-key /api-reference/openapi.json post /secrets/api/keys Use when issuing a new API key (e.g. for a service account or for the current user). Provide service_account_id to create for a service account. # Delete an API key Source: https://docs.flexprice.io/api-reference/secrets/delete-an-api-key /api-reference/openapi.json delete /secrets/api/keys/{id} Use when revoking an API key (e.g. rotation or compromise). Permanently invalidates the key. # List API keys Source: https://docs.flexprice.io/api-reference/secrets/list-api-keys /api-reference/openapi.json get /secrets/api/keys Use when listing API keys (e.g. admin view or rotating keys). Returns a paginated list. # Activate draft subscription Source: https://docs.flexprice.io/api-reference/subscriptions/activate-draft-subscription /api-reference/openapi.json post /subscriptions/{id}/activate Use when turning a draft subscription live (e.g. after collecting payment or completing setup). Once activated, billing and entitlements apply. # Add addon to subscription Source: https://docs.flexprice.io/api-reference/subscriptions/add-addon-to-subscription /api-reference/openapi.json post /subscriptions/addon Use when adding an optional product or add-on to an existing subscription (e.g. extra storage or support tier). # Cancel subscription Source: https://docs.flexprice.io/api-reference/subscriptions/cancel-subscription /api-reference/openapi.json post /subscriptions/{id}/cancel Use when a customer churns or downgrades. Supports immediate or end-of-period cancellation and proration. Ideal for self-serve or support-driven cancellations. # Cancel subscription schedule Source: https://docs.flexprice.io/api-reference/subscriptions/cancel-subscription-schedule /api-reference/openapi.json post /v1/subscriptions/schedules/{schedule_id}/cancel Use when cancelling a scheduled change (e.g. customer changed mind). Identify by schedule ID in path or by subscription ID + schedule type in body. # Create subscription Source: https://docs.flexprice.io/api-reference/subscriptions/create-subscription /api-reference/openapi.json post /subscriptions Use when onboarding a customer to a plan or starting a new subscription. Ideal for draft subscriptions (activate later) or active from start. # Create subscription line item Source: https://docs.flexprice.io/api-reference/subscriptions/create-subscription-line-item /api-reference/openapi.json post /subscriptions/{id}/lineitems Use when adding a new charge or seat to a subscription (e.g. extra seat or one-time add). Supports price_id or inline price. # Delete subscription line item Source: https://docs.flexprice.io/api-reference/subscriptions/delete-subscription-line-item /api-reference/openapi.json delete /subscriptions/lineitems/{id} Use when removing a charge or seat from a subscription (e.g. downgrade). Line item ends; retained for history but no longer billed. # Execute subscription modification Source: https://docs.flexprice.io/api-reference/subscriptions/execute-subscription-modification /api-reference/openapi.json post /subscriptions/{id}/modify/execute Execute a mid-cycle subscription modification (inheritance, quantity change, grouped invoicing, trial end, coupon, or tax). # Execute subscription plan change Source: https://docs.flexprice.io/api-reference/subscriptions/execute-subscription-plan-change /api-reference/openapi.json post /subscriptions/{id}/change/execute Use when applying a plan change (e.g. upgrade or downgrade). Executes proration and generates invoice or credit as needed. # Get active addon associations Source: https://docs.flexprice.io/api-reference/subscriptions/get-active-addon-associations /api-reference/openapi.json get /subscriptions/{id}/addons/associations Use when listing which add-ons are currently attached to a subscription (e.g. for display or editing). # Get subscription Source: https://docs.flexprice.io/api-reference/subscriptions/get-subscription /api-reference/openapi.json get /subscriptions/{id} Use when you need to load a single subscription (e.g. for a billing portal or to check status). # Get subscription entitlements Source: https://docs.flexprice.io/api-reference/subscriptions/get-subscription-entitlements /api-reference/openapi.json get /subscriptions/{id}/entitlements Use when checking what features or limits a subscription has (e.g. entitlement checks or feature gating). Optional feature_ids to filter. # Get subscription schedule Source: https://docs.flexprice.io/api-reference/subscriptions/get-subscription-schedule /api-reference/openapi.json get /v1/subscription-schedules/{id} Use when you need to load a single scheduled change (e.g. to show when a plan change or renewal takes effect). # Get subscription (V2) Source: https://docs.flexprice.io/api-reference/subscriptions/get-subscription-v2 /api-reference/openapi.json get /subscriptions/{id}/v2 Use when you need a subscription with related data (line items, prices, plan). Supports expand for detailed payloads without extra round-trips. # Get upcoming credit grant applications Source: https://docs.flexprice.io/api-reference/subscriptions/get-upcoming-credit-grant-applications /api-reference/openapi.json get /subscriptions/{id}/grants/upcoming Use when showing upcoming or pending credits for a subscription (e.g. in a portal or for forecasting). # Get usage by subscription Source: https://docs.flexprice.io/api-reference/subscriptions/get-usage-by-subscription /api-reference/openapi.json post /subscriptions/usage Use when showing usage for a subscription (e.g. in a portal or for overage checks). Supports time range and filters. # List all subscription schedules Source: https://docs.flexprice.io/api-reference/subscriptions/list-all-subscription-schedules /api-reference/openapi.json get /v1/subscription-schedules Use when listing or searching scheduled changes across subscriptions (e.g. admin view). Returns schedules with optional filtering. # List subscription schedules Source: https://docs.flexprice.io/api-reference/subscriptions/list-subscription-schedules /api-reference/openapi.json get /v1/subscriptions/{subscription_id}/schedules Use when listing scheduled changes for a subscription (e.g. upcoming plan change or renewal). Returns all schedules for that subscription. # Preview subscription modification Source: https://docs.flexprice.io/api-reference/subscriptions/preview-subscription-modification /api-reference/openapi.json post /subscriptions/{id}/modify/preview Preview the impact of a mid-cycle subscription modification (inheritance, quantity change, grouped invoicing, trial end, coupon, or tax) without committing changes. # Preview subscription plan change Source: https://docs.flexprice.io/api-reference/subscriptions/preview-subscription-plan-change /api-reference/openapi.json post /subscriptions/{id}/change/preview Use when showing a customer the cost of a plan change before they confirm (e.g. upgrade/downgrade preview with proration). # Query subscriptions Source: https://docs.flexprice.io/api-reference/subscriptions/query-subscriptions /api-reference/openapi.json post /subscriptions/search Use when listing or searching subscriptions (e.g. admin view or customer subscription list). Returns a paginated list; supports filtering by customer, plan, status. # Remove addon from subscription Source: https://docs.flexprice.io/api-reference/subscriptions/remove-addon-from-subscription /api-reference/openapi.json delete /subscriptions/addon Use when removing an add-on from a subscription (e.g. downgrade or opt-out). # Search subscription line items Source: https://docs.flexprice.io/api-reference/subscriptions/search-subscription-line-items /api-reference/openapi.json post /subscriptions/lineitems/search List subscription line items with a JSON filter (subscription, customer, price, pagination, expand=prices, etc.). # Update subscription Source: https://docs.flexprice.io/api-reference/subscriptions/update-subscription /api-reference/openapi.json put /subscriptions/{id} Use when changing subscription details (e.g. quantity, billing anchor, or parent). Supports partial update; send "" to clear parent_subscription_id. # Update subscription line item Source: https://docs.flexprice.io/api-reference/subscriptions/update-subscription-line-item /api-reference/openapi.json put /subscriptions/lineitems/{id} Use when changing a subscription line item (e.g. quantity or price). Implemented by ending the current line and creating a new one for clean billing. # Create a new task Source: https://docs.flexprice.io/api-reference/tasks/create-a-new-task /api-reference/openapi.json post /tasks Use when submitting a file or job for async processing (e.g. export or import). Returns task ID to poll for status and result. # Download task export file Source: https://docs.flexprice.io/api-reference/tasks/download-task-export-file /api-reference/openapi.json get /tasks/{id}/download Use when letting a user download an exported file (e.g. report or data export). Returns a presigned URL; supports FlexPrice or customer-owned S3. # Get a task Source: https://docs.flexprice.io/api-reference/tasks/get-a-task /api-reference/openapi.json get /tasks/{id} Use when checking task status or progress (e.g. polling after create). Returns task by ID. # Get task processing result Source: https://docs.flexprice.io/api-reference/tasks/get-task-processing-result /api-reference/openapi.json get /tasks/result Use when fetching the outcome of a completed task (e.g. export URL or error message). Call after task status is complete. # List tasks Source: https://docs.flexprice.io/api-reference/tasks/list-tasks /api-reference/openapi.json get /tasks Use when listing or searching async tasks (e.g. admin queue view). Returns list with optional filtering. # Update task status Source: https://docs.flexprice.io/api-reference/tasks/update-task-status /api-reference/openapi.json put /tasks/{id}/status Use when updating task status (e.g. marking complete or failed from a worker). Typically called by backend processors. # Create Tax Association Source: https://docs.flexprice.io/api-reference/tax-associations/create-tax-association /api-reference/openapi.json post /taxes/associations Use when linking a tax rate to an entity (e.g. customer, product, or region) so that rate applies on invoices. # Delete tax association Source: https://docs.flexprice.io/api-reference/tax-associations/delete-tax-association /api-reference/openapi.json delete /taxes/associations/{id} Use when removing a tax association (e.g. entity no longer subject to that rate). # Get Tax Association Source: https://docs.flexprice.io/api-reference/tax-associations/get-tax-association /api-reference/openapi.json get /taxes/associations/{id} Use when you need to load a single tax association (e.g. for display or editing). # List tax associations Source: https://docs.flexprice.io/api-reference/tax-associations/list-tax-associations /api-reference/openapi.json get /taxes/associations Use when listing tax associations (e.g. tax config or audit). Returns list with optional filtering. # Update tax association Source: https://docs.flexprice.io/api-reference/tax-associations/update-tax-association /api-reference/openapi.json put /taxes/associations/{id} Use when changing a tax association (e.g. switch rate or entity). Request body contains the fields to update. # Create a tax rate Source: https://docs.flexprice.io/api-reference/tax-rates/create-a-tax-rate /api-reference/openapi.json post /taxes/rates Use when defining a new tax rate (e.g. VAT or sales tax) for use in invoices. Attach to customers or products via tax associations. # Delete a tax rate Source: https://docs.flexprice.io/api-reference/tax-rates/delete-a-tax-rate /api-reference/openapi.json delete /taxes/rates/{id} Use when retiring a tax rate (e.g. no longer applicable). Fails if still referenced by associations. # Get a tax rate Source: https://docs.flexprice.io/api-reference/tax-rates/get-a-tax-rate /api-reference/openapi.json get /taxes/rates/{id} Use when you need to load a single tax rate (e.g. for display or when creating an association). # Get tax rates Source: https://docs.flexprice.io/api-reference/tax-rates/get-tax-rates /api-reference/openapi.json get /taxes/rates Use when listing tax rates (e.g. tax config UI). Returns tax rates with optional filters. # Update a tax rate Source: https://docs.flexprice.io/api-reference/tax-rates/update-a-tax-rate /api-reference/openapi.json put /taxes/rates/{id} Use when changing a tax rate (e.g. rate value or name). Request body contains the fields to update. # Get billing usage for the current tenant Source: https://docs.flexprice.io/api-reference/tenants/get-billing-usage-for-the-current-tenant /api-reference/openapi.json get /tenant/billing Use when showing the current tenant's billing usage (e.g. admin billing page or usage caps). Returns subscription and usage for the tenant. # Get tenant by ID Source: https://docs.flexprice.io/api-reference/tenants/get-tenant-by-id /api-reference/openapi.json get /tenants/{id} Get tenant by ID # Update a tenant Source: https://docs.flexprice.io/api-reference/tenants/update-a-tenant /api-reference/openapi.json put /tenants/update Use when changing tenant details (e.g. name or billing info). Request body contains the fields to update. # Create user or service account Source: https://docs.flexprice.io/api-reference/users/create-user-or-service-account /api-reference/openapi.json post /users Create a user account (type=user, email required; returns user + password for login) or a service account (type=service_account, roles required) for API/automation access. # Delete service account Source: https://docs.flexprice.io/api-reference/users/delete-service-account /api-reference/openapi.json delete /users/{id} Soft-delete (archive) a service account by ID. # Get current user Source: https://docs.flexprice.io/api-reference/users/get-current-user /api-reference/openapi.json get /users/me Use to show the logged-in user's profile in the UI or to check permissions and roles for the current session. # Query users Source: https://docs.flexprice.io/api-reference/users/query-users /api-reference/openapi.json post /users/search Use when listing or searching service accounts in an admin UI, or when auditing who has API access and which roles they have. # Update current user Source: https://docs.flexprice.io/api-reference/users/update-current-user /api-reference/openapi.json put /users/me Update the current authenticated user. Supports name and metadata updates. # Update service account Source: https://docs.flexprice.io/api-reference/users/update-service-account /api-reference/openapi.json put /users/{id} Update a service account by ID (name only). # Create a new wallet Source: https://docs.flexprice.io/api-reference/wallets/create-a-new-wallet /api-reference/openapi.json post /wallets Use when giving a customer a prepaid or credit balance (e.g. prepaid plans or promotional credits). # Get Customer Wallets Source: https://docs.flexprice.io/api-reference/wallets/get-customer-wallets /api-reference/openapi.json get /customers/wallets Use when resolving wallets by external customer id or lookup key (e.g. from your app's user id). Supports optional real-time balance and expand. # Get wallet Source: https://docs.flexprice.io/api-reference/wallets/get-wallet /api-reference/openapi.json get /wallets/{id} Use when you need to load a single wallet (e.g. for a balance or settings view). # Get wallet balance Source: https://docs.flexprice.io/api-reference/wallets/get-wallet-balance /api-reference/openapi.json get /wallets/{id}/balance/real-time Use when displaying or checking current wallet balance (e.g. before charging or in a portal). Supports optional expand for credits breakdown and from_cache. # Get wallet transactions Source: https://docs.flexprice.io/api-reference/wallets/get-wallet-transactions /api-reference/openapi.json get /wallets/{id}/transactions Use when showing transaction history for a wallet (e.g. credit/debit log or audit). Returns a paginated list; supports limit, offset, and filters. # Get wallets by customer ID Source: https://docs.flexprice.io/api-reference/wallets/get-wallets-by-customer-id /api-reference/openapi.json get /customers/{id}/wallets Use when showing a customer's wallets (e.g. balance overview by currency or in a billing portal). Supports optional expand for balance breakdown. # Query wallet transactions Source: https://docs.flexprice.io/api-reference/wallets/query-wallet-transactions /api-reference/openapi.json post /wallets/transactions/search Use when searching or reporting on wallet transactions (e.g. cross-wallet history or reconciliation). Returns a paginated list; supports filtering by wallet, customer, type, date range. # Query wallets Source: https://docs.flexprice.io/api-reference/wallets/query-wallets /api-reference/openapi.json post /wallets/search Use when listing or searching wallets (e.g. admin view or reporting). Returns a paginated list; supports filtering by customer and status. # Terminate a wallet Source: https://docs.flexprice.io/api-reference/wallets/terminate-a-wallet /api-reference/openapi.json post /wallets/{id}/terminate Use when closing a customer wallet (e.g. churn or migration). Closes the wallet and applies remaining balance per policy (refund or forfeit). # Top up wallet Source: https://docs.flexprice.io/api-reference/wallets/top-up-wallet /api-reference/openapi.json post /wallets/{id}/top-up Use when adding funds to a wallet (e.g. top-up, refund, or manual credit). Supports optional idempotency via reference. # Update a wallet Source: https://docs.flexprice.io/api-reference/wallets/update-a-wallet /api-reference/openapi.json put /wallets/{id} Use when changing wallet settings (e.g. enabling or updating auto top-up thresholds). # credit_note.created Source: https://docs.flexprice.io/api-reference/webhook-events/credit_notecreated /api-reference/openapi.json post /webhook-events/credit_note.created Fired when a new credit note is created. Doc-only for parsing. # credit_note.updated Source: https://docs.flexprice.io/api-reference/webhook-events/credit_noteupdated /api-reference/openapi.json post /webhook-events/credit_note.updated Fired when a credit note is updated. Doc-only for parsing. # customer.created Source: https://docs.flexprice.io/api-reference/webhook-events/customercreated /api-reference/openapi.json post /webhook-events/customer.created Fired when a new customer is created. Doc-only for parsing. # customer.deleted Source: https://docs.flexprice.io/api-reference/webhook-events/customerdeleted /api-reference/openapi.json post /webhook-events/customer.deleted Fired when a customer is deleted. Doc-only for parsing. # customer.updated Source: https://docs.flexprice.io/api-reference/webhook-events/customerupdated /api-reference/openapi.json post /webhook-events/customer.updated Fired when a customer is updated. Doc-only for parsing. # entitlement.created Source: https://docs.flexprice.io/api-reference/webhook-events/entitlementcreated /api-reference/openapi.json post /webhook-events/entitlement.created Fired when a new entitlement is created. Doc-only for parsing. # entitlement.deleted Source: https://docs.flexprice.io/api-reference/webhook-events/entitlementdeleted /api-reference/openapi.json post /webhook-events/entitlement.deleted Fired when an entitlement is deleted. Doc-only for parsing. # entitlement.updated Source: https://docs.flexprice.io/api-reference/webhook-events/entitlementupdated /api-reference/openapi.json post /webhook-events/entitlement.updated Fired when an entitlement is updated. Doc-only for parsing. # feature.created Source: https://docs.flexprice.io/api-reference/webhook-events/featurecreated /api-reference/openapi.json post /webhook-events/feature.created Fired when a new feature is created. Doc-only for parsing. # feature.deleted Source: https://docs.flexprice.io/api-reference/webhook-events/featuredeleted /api-reference/openapi.json post /webhook-events/feature.deleted Fired when a feature is deleted. Doc-only for parsing. # feature.updated Source: https://docs.flexprice.io/api-reference/webhook-events/featureupdated /api-reference/openapi.json post /webhook-events/feature.updated Fired when a feature is updated. Doc-only for parsing. # feature.wallet_balance.alert Source: https://docs.flexprice.io/api-reference/webhook-events/featurewallet_balancealert /api-reference/openapi.json post /webhook-events/feature.wallet_balance.alert Fired when a feature's associated wallet balance crosses an alert threshold. Doc-only for parsing. # invoice.communication.triggered Source: https://docs.flexprice.io/api-reference/webhook-events/invoicecommunicationtriggered /api-reference/openapi.json post /webhook-events/invoice.communication.triggered Fired when an invoice communication (e.g. email notification) is triggered. Doc-only for parsing. # invoice.payment.overdue Source: https://docs.flexprice.io/api-reference/webhook-events/invoicepaymentoverdue /api-reference/openapi.json post /webhook-events/invoice.payment.overdue Fired when an invoice payment is overdue past the due date. Doc-only for parsing. # invoice.update Source: https://docs.flexprice.io/api-reference/webhook-events/invoiceupdate /api-reference/openapi.json post /webhook-events/invoice.update Fired when an invoice is updated. Doc-only for parsing. # invoice.update.finalized Source: https://docs.flexprice.io/api-reference/webhook-events/invoiceupdatefinalized /api-reference/openapi.json post /webhook-events/invoice.update.finalized Fired when an invoice is finalized and locked for payment. Doc-only for parsing. # invoice.update.payment Source: https://docs.flexprice.io/api-reference/webhook-events/invoiceupdatepayment /api-reference/openapi.json post /webhook-events/invoice.update.payment Fired when an invoice payment status changes. Doc-only for parsing. # invoice.update.voided Source: https://docs.flexprice.io/api-reference/webhook-events/invoiceupdatevoided /api-reference/openapi.json post /webhook-events/invoice.update.voided Fired when an invoice is voided (e.g. order cancelled or duplicate). Doc-only for parsing. # payment.created Source: https://docs.flexprice.io/api-reference/webhook-events/paymentcreated /api-reference/openapi.json post /webhook-events/payment.created Fired when a new payment is created. Doc-only for parsing. # payment.failed Source: https://docs.flexprice.io/api-reference/webhook-events/paymentfailed /api-reference/openapi.json post /webhook-events/payment.failed Fired when a payment fails. Doc-only for parsing. # payment.pending Source: https://docs.flexprice.io/api-reference/webhook-events/paymentpending /api-reference/openapi.json post /webhook-events/payment.pending Fired when a payment is pending processing. Doc-only for parsing. # payment.success Source: https://docs.flexprice.io/api-reference/webhook-events/paymentsuccess /api-reference/openapi.json post /webhook-events/payment.success Fired when a payment succeeds. Doc-only for parsing. # payment.updated Source: https://docs.flexprice.io/api-reference/webhook-events/paymentupdated /api-reference/openapi.json post /webhook-events/payment.updated Fired when a payment is updated. Doc-only for parsing. # subscription.activated Source: https://docs.flexprice.io/api-reference/webhook-events/subscriptionactivated /api-reference/openapi.json post /webhook-events/subscription.activated Fired when a draft subscription is activated. Doc-only for parsing. # subscription.cancelled Source: https://docs.flexprice.io/api-reference/webhook-events/subscriptioncancelled /api-reference/openapi.json post /webhook-events/subscription.cancelled Fired when a subscription is cancelled (immediately or end-of-period). Doc-only for parsing. # subscription.created Source: https://docs.flexprice.io/api-reference/webhook-events/subscriptioncreated /api-reference/openapi.json post /webhook-events/subscription.created Fired when a new subscription is created. Doc-only for parsing. # subscription.draft.created Source: https://docs.flexprice.io/api-reference/webhook-events/subscriptiondraftcreated /api-reference/openapi.json post /webhook-events/subscription.draft.created Fired when a new draft subscription is created (not yet activated). Doc-only for parsing. # subscription.paused Source: https://docs.flexprice.io/api-reference/webhook-events/subscriptionpaused /api-reference/openapi.json post /webhook-events/subscription.paused Fired when a subscription is paused. Doc-only for parsing. # subscription.phase.created Source: https://docs.flexprice.io/api-reference/webhook-events/subscriptionphasecreated /api-reference/openapi.json post /webhook-events/subscription.phase.created Fired when a new subscription phase is created. Doc-only for parsing. # subscription.phase.deleted Source: https://docs.flexprice.io/api-reference/webhook-events/subscriptionphasedeleted /api-reference/openapi.json post /webhook-events/subscription.phase.deleted Fired when a subscription phase is deleted. Doc-only for parsing. # subscription.phase.updated Source: https://docs.flexprice.io/api-reference/webhook-events/subscriptionphaseupdated /api-reference/openapi.json post /webhook-events/subscription.phase.updated Fired when a subscription phase is updated. Doc-only for parsing. # subscription.renewal.due Source: https://docs.flexprice.io/api-reference/webhook-events/subscriptionrenewaldue /api-reference/openapi.json post /webhook-events/subscription.renewal.due Fired when a subscription renewal is upcoming (cron-driven). Doc-only for parsing. # subscription.resumed Source: https://docs.flexprice.io/api-reference/webhook-events/subscriptionresumed /api-reference/openapi.json post /webhook-events/subscription.resumed Fired when a paused subscription is resumed. Doc-only for parsing. # subscription.updated Source: https://docs.flexprice.io/api-reference/webhook-events/subscriptionupdated /api-reference/openapi.json post /webhook-events/subscription.updated Fired when a subscription is updated (e.g. quantity, billing anchor, or metadata changes). Doc-only for parsing. # wallet.created Source: https://docs.flexprice.io/api-reference/webhook-events/walletcreated /api-reference/openapi.json post /webhook-events/wallet.created Fired when a new wallet is created. Doc-only for parsing. # wallet.credit_balance.dropped Source: https://docs.flexprice.io/api-reference/webhook-events/walletcredit_balancedropped /api-reference/openapi.json post /webhook-events/wallet.credit_balance.dropped Fired when a wallet's credit balance drops below an alert threshold. Doc-only for parsing. # wallet.credit_balance.recovered Source: https://docs.flexprice.io/api-reference/webhook-events/walletcredit_balancerecovered /api-reference/openapi.json post /webhook-events/wallet.credit_balance.recovered Fired when a wallet's credit balance recovers above an alert threshold. Doc-only for parsing. # wallet.ongoing_balance.dropped Source: https://docs.flexprice.io/api-reference/webhook-events/walletongoing_balancedropped /api-reference/openapi.json post /webhook-events/wallet.ongoing_balance.dropped Fired when a wallet's ongoing balance drops below an alert threshold. Doc-only for parsing. # wallet.ongoing_balance.recovered Source: https://docs.flexprice.io/api-reference/webhook-events/walletongoing_balancerecovered /api-reference/openapi.json post /webhook-events/wallet.ongoing_balance.recovered Fired when a wallet's ongoing balance recovers above an alert threshold. Doc-only for parsing. # wallet.terminated Source: https://docs.flexprice.io/api-reference/webhook-events/walletterminated /api-reference/openapi.json post /webhook-events/wallet.terminated Fired when a wallet is terminated. Doc-only for parsing. # wallet.transaction.created Source: https://docs.flexprice.io/api-reference/webhook-events/wallettransactioncreated /api-reference/openapi.json post /webhook-events/wallet.transaction.created Fired when a new wallet transaction is created (top-up, deduction, etc.). Doc-only for parsing. # wallet.updated Source: https://docs.flexprice.io/api-reference/webhook-events/walletupdated /api-reference/openapi.json post /webhook-events/wallet.updated Fired when a wallet is updated. Doc-only for parsing. # Query workflows Source: https://docs.flexprice.io/api-reference/workflows/query-workflows /api-reference/openapi.json post /workflows/search Use when listing or auditing workflow runs (e.g. ops dashboard or debugging). Returns a paginated list; supports filtering by workflow type and status. # Ingesting AI Usage Source: https://docs.flexprice.io/docs/ai-cost-tracking/ingesting-ai-usage A technical look at how Flexprice normalises usage from any AI gateway or provider into a single standardised event, attaches cost, and auto-provisions metering — across real-time push, managed pull, and aggregate sources. This page extends the [architecture overview](/docs/ai-cost-tracking/overview#architecture) with the mechanics of ingestion: the standardised event every source maps to, the connector types, how cost is attached, and how metering is provisioned for you. The design goal is **zero manual setup**. You connect a source; Flexprice handles normalisation, pricing, and the creation of meters and features — you do not hand-write transforms, build a model price list, or define meters per model. ## The standardised AI usage event Every source — a LiteLLM webhook, a Langfuse trace, a Bedrock log, a Databricks usage row — is normalised to one canonical shape before anything else happens. It rides on Flexprice's standard [event](/docs/event-ingestion/overview) (`event_name`, `external_customer_id`, `properties`, `timestamp`, `source`), with a fixed convention for AI properties. ```json theme={null} { "event_name": "ai.usage", "external_customer_id": "team_platform", "timestamp": "2026-06-14T10:30:00Z", "source": "litellm", "properties": { "provider": "anthropic", "model": "claude-opus-4-8", "operation": "chat", "input_tokens": "1840", "output_tokens": "320", "cached_tokens": "1024", "reasoning_tokens": "0", "reported_cost": "0.041", "request_id": "req_abc123", "raw_user": "u_91", "raw_team": "team_platform", "agent_id": "agent_support_bot", "fidelity": "per_request" } } ``` Key fields: | Field | Purpose | | -------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- | | `provider`, `model`, `operation` | Drive model pricing and let analytics break down by provider/model/token type. | | `input_tokens`, `output_tokens`, `cached_tokens`, `reasoning_tokens` | Token-level usage; `unit`/`quantity` are used for non-token sources (characters, seconds, requests, credits). | | `reported_cost` | The source's own computed cost in USD, when it provides one (most gateways do). | | `raw_user`, `raw_team`, `agent_id`, `tags` | Source identifiers used to resolve the billing entity and hierarchy. | | `fidelity` | `per_request` or `aggregate` — records the granularity a source can offer, so analytics stays honest. | Following Flexprice's [event format](/docs/collectors/how-it-works#data-transformation), all `properties` values are sent as strings; Flexprice interprets numeric fields during aggregation. ## Connector types Sources differ in whether they can push in real time, must be polled, or only expose aggregate totals. Flexprice covers all three with the same downstream pipeline. | Type | How it works | Best for | Latency | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ---------------- | | **Push (edge)** | The source POSTs each request to a [Flexprice Collector](/docs/collectors/overview) running in your infra, which transforms and forwards it. | Real-time sources and data that must stay in-network. | Real-time | | **Managed pull** | Flexprice runs a scheduled job that reads the source over API, SQL, or object storage, transforms server-side, and ingests. | API/SQL/log-reachable SaaS and clouds — **no deployment**. | Seconds to hours | | **Aggregate** | Flexprice ingests periodic exports (dashboard/email/usage views) and converts native units to cost. | Sources with no per-request API. | Daily | ### Push flow (e.g. LiteLLM) Push flow — LiteLLM pushes a per-request webhook to the Flexprice Collector, which transforms it to ai.usage and forwards it through metering and pricing to wallets, alerts, and analytics; a budget breach calls back to LiteLLM via the management API. The collector is the existing [Bento-based](/docs/collectors/how-it-works) pipeline — inputs → processors → outputs — with a ready-made transform for the source, so you are not writing Bloblang by hand. ### Managed pull flow (e.g. Langfuse, Databricks, Bedrock) Managed pull flow — a scheduled trigger reads each source since the last watermark via API, SQL, or object-store readers, transforms the result to ai.usage server-side, dedupes by request_id, and ingests into Flexprice. You provide read-only credentials once; Flexprice stores them securely and runs the schedule. A per-source **watermark** plus `request_id` **dedupe** guarantees no double counting across overlapping pulls. ## Integrating LiteLLM LiteLLM is a real-time **push** source: it fires a logging callback on every completion, already carrying token counts, USD cost, model, and identity. There are three ways to wire it into Flexprice — from drop-in to fully custom — and **all three emit the same standardised `ai.usage` event**. You never define meters, features, or prices for this; enabling the LiteLLM connector with a [template](#auto-provisioning-metering) provisions them. ### Option 1 — Flexprice SDK callback (recommended) Register the Flexprice callback and you're done — no payload mapping, no transform to maintain. Every `completion()` call you already make is metered. ```python theme={null} import litellm from flexprice.litellm import FlexpriceLogger litellm.callbacks = [ FlexpriceLogger( api_key="", # which LiteLLM identity becomes the Flexprice billing entity entity_from="metadata.user_api_key_team_id", ) ] # nothing else changes — usage now flows to Flexprice on every call litellm.completion( model="anthropic/claude-opus-4-8", messages=[{"role": "user", "content": "Summarise this ticket"}], metadata={"user_api_key_team_id": "team_platform", "user_api_key_user_id": "u_91"}, ) ``` The callback reads LiteLLM's per-request record, maps it to `ai.usage`, and ships it — one event per request, attributed to the team (with the user kept as a child for per-user and per-agent visibility). ### Option 2 — LiteLLM Proxy (no code) If you run the LiteLLM **Proxy**, enable the Flexprice callback in `config.yaml`. Every request routed through the proxy — from any app, agent, or MCP server — is metered and attributed to the virtual key's team/user, with nothing to deploy in your services. ```yaml theme={null} litellm_settings: callbacks: ["flexprice"] environment_variables: FLEXPRICE_API_KEY: "" # map a LiteLLM virtual-key dimension to the billing entity FLEXPRICE_ENTITY_FROM: "metadata.user_api_key_team_id" ``` When usage must stay inside your network, point LiteLLM's logging webhook at your in-infra [Flexprice Collector](/docs/collectors/overview) instead — the collector applies the same transform before anything leaves your VPC. ### Option 3 — Custom callback (full control) When you want to shape the event yourself — add tags, override the entity, or filter calls — implement a LiteLLM `CustomLogger` and send the event with the Flexprice SDK. This is exactly what Option 1 does under the hood, exposed for you to customise. ```python theme={null} from litellm.integrations.custom_logger import CustomLogger import litellm from flexprice import Flexprice fp = Flexprice(api_key="") class FlexpriceLogger(CustomLogger): async def async_log_success_event(self, kwargs, response_obj, start_time, end_time): # LiteLLM's normalised, per-request usage + cost record slp = kwargs["standard_logging_object"] meta = slp.get("metadata", {}) fp.events.ingest( event_name="ai.usage", external_customer_id=meta.get("user_api_key_team_id", "unknown"), source="litellm", properties={ "provider": slp["custom_llm_provider"], "model": slp["model"], "operation": "chat", "input_tokens": slp["prompt_tokens"], "output_tokens": slp["completion_tokens"], "reported_cost": slp["response_cost"], # USD, computed by LiteLLM "request_id": slp["id"], "raw_user": meta.get("user_api_key_user_id"), "raw_team": meta.get("user_api_key_team_id"), }, ) litellm.callbacks = [FlexpriceLogger()] ``` ### The event Flexprice receives Whichever option you pick, the same canonical event lands — already mapped, ready to be priced and attributed: ```json theme={null} { "event_name": "ai.usage", "external_customer_id": "team_platform", "source": "litellm", "properties": { "provider": "anthropic", "model": "claude-opus-4-8", "operation": "chat", "input_tokens": "1840", "output_tokens": "320", "reported_cost": "0.041", "request_id": "req_abc123", "raw_user": "u_91", "raw_team": "team_platform" } } ``` If you'd rather not use an SDK at all, the same event can be posted directly to the [events API](/docs/event-ingestion/sending-events): ```bash theme={null} curl -X POST https://api.cloud.flexprice.io/v1/events \ -H 'x-api-key: ' \ -H 'Content-Type: application/json' \ -d '{ "event_name": "ai.usage", "external_customer_id": "team_platform", "source": "litellm", "properties": { "provider": "anthropic", "model": "claude-opus-4-8", "input_tokens": "1840", "output_tokens": "320", "reported_cost": "0.041", "request_id": "req_abc123" } }' ``` ### Closing the loop — enforcement Because Flexprice now holds the source of truth for spend, a budget breach can act **back** on LiteLLM (the dashed return path in the push-flow diagram). On a `warning`/`critical` [alert](/docs/customers/threshold-notifications), Flexprice calls LiteLLM's management API with your master key to throttle the offending key or team: ```bash theme={null} # soft: zero out the remaining budget on a key curl -X POST https:///key/update \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "key": "", "max_budget": 0 }' # hard: block the key outright curl -X POST https:///key/block \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "key": "" }' ``` Start with soft alerts (webhooks/notifications) and graduate to hard enforcement once you trust the numbers. ## How cost is attached Most modern gateways compute USD cost themselves, so Flexprice uses that by default and re-prices only when it needs to. * **Trust the source cost (default)** — when `reported_cost` is present (LiteLLM, OpenRouter, Portkey, Langfuse, Helicone, Cloudflare, Vercel), Flexprice stores it as-is. This correctly captures negotiated and BYOK rates. * **Re-price from the catalog** — when a source provides **tokens only** (Bedrock logs, Databricks token usage) or you want **markup/margin** or a **single normalised price** across gateways, Flexprice prices from its model pricing repository. The **public model pricing repository is refreshed daily**, so popular provider/model rates are available out of the box. Override any rate per provider, model, or customer for committed pricing, and apply a markup factor when reselling. How cost is attached — if the ai.usage event already carries reported_cost, Flexprice uses the source cost; otherwise (or when markup or normalised pricing is needed) it prices from the model pricing repository and applies overrides and markup, producing the charged cost on usage. ## Auto-provisioning metering Enabling a connector and choosing a **template** creates the metering graph for you — no manual meters, features, or prices. * **`cost_tracking`** — meters for input/output/cached/reasoning tokens and request count, features per dimension, and catalog pricing at cost. For internal showback. * **`team_budget`** — the above plus a wallet, a recurring monthly credit grant, and `info`/`warning`/`critical` [alerts](/docs/customers/threshold-notifications) wired to gateway enforcement. For team and agent budgets. * **`resale_markup`** — catalog re-pricing with a configurable margin, plus margin analytics. For AI features you bill customers for. Provider and model breakdowns come from filters and `group_by` on the `provider` and `model` properties, so there is no meter-per-model explosion — a handful of generic meters cover every model. ## Identity and hierarchy resolution Each connector maps source identifiers to a Flexprice billing entity and, optionally, builds the hierarchy: * A primary field (for example `raw_team`) resolves to the [customer](/docs/customers/overview) being metered. * Additional fields (`raw_user`, `agent_id`) create child entities under it, using [Customer Hierarchy](/docs/subscriptions/customer-hierarchy) for individual visibility with consolidated rollups and shared wallets. * Unrecognised identifiers can auto-create entities or map to an existing `external_id`. This is what lets one wallet span an entire team while you still see each user's and agent's usage separately — and what powers per-agent ROI. ## Source coverage Phase one deliberately spans every connector type so the model is proven against the hard cases: | Source | Type | Cost basis | Notes | | ------------------------- | --------------------------------- | ------------------- | ----------------------------------------------------------------- | | **LiteLLM** | Push (or hosted webhook) | Source cost | Richest identity (key/user/team/org/tags); real-time. | | **Langfuse** | Managed pull (observations API) | Source cost | No usage webhook upstream, so polled; near-real-time. | | **AWS Bedrock** | Managed pull (S3 logs + CUR) | Catalog re-price | Per-request token logs; reconcile \$ against Cost & Usage Report. | | **Databricks** | Managed pull (system tables, SQL) | Catalog re-price | DBU/token usage joined to list prices; hourly grain. | | **Salesforce Agentforce** | Aggregate | Catalog (rate card) | Native units (credits/conversations); `fidelity: aggregate`. | Coverage expands continuously — Helicone, OpenRouter, Portkey, Snowflake Cortex, TrueFoundry, Cloudflare, Vercel, and SAP Joule follow the same connector model. Each new source is a connector definition plus pricing entries, not a change to your setup. Aggregate sources (such as Agentforce and SAP Joule) report coarse, native-unit totals rather than per-request tokens. Flexprice labels these with `fidelity: aggregate` so dashboards and alerts reflect the real granularity — treat per-agent ROI from these sources as approximate. ## Related The problem, the solution, and the high-level architecture. The Bento-based collector used for push and in-infra sources. The underlying event pipeline AI usage rides on. Spend thresholds, states, and webhook delivery. # Overview Source: https://docs.flexprice.io/docs/ai-cost-tracking/overview Turn fragmented AI spend across gateways, clouds, and SaaS into one metered, priced, and governed view — internal cost by team, user, and application, plus customer-level cost and margin. AI Cost Tracking makes Flexprice the single source of truth for AI usage. You stream usage from any AI gateway or provider, Flexprice attaches cost, and you get token-level visibility, spend alerts and limits, wallets, and margin analytics — at every level of your organization and for every customer you bill. ## Problem statement AI spend is sprawling and hard to attribute. A single company typically consumes models through many disconnected paths at once: * **Gateways** — LiteLLM, OpenRouter, Portkey * **Observability layers** — Langfuse, Helicone * **Clouds and platforms** — AWS Bedrock, Databricks, Snowflake Cortex * **Packaged SaaS AI** — Salesforce Agentforce, SAP Joule Each path has its own usage format, its own identifiers, and its own price list. That makes three questions surprisingly hard to answer: 1. **Internal cost** — what is each **team**, **user**, **application**, or **agent** spending on AI, and is it within budget? 2. **Customer cost** — for the AI features you resell, how much does serving each **customer** actually cost? 3. **Margin** — what is your AI **margin by revenue** once provider cost is subtracted from what you charge? Without a common layer, teams stitch this together by hand — exporting CSVs, reconciling token counts, and guessing at cost. Flexprice replaces that with one ingestion and metering layer. ## Solution Flexprice acts as a single point of AI usage ingestion. Once usage flows in, everything else is built on top of the same metered data: * **Feature usage at the customer level** — every request is attributed to a billing entity (customer, team, user, or agent), so usage rolls up cleanly for showback, entitlements, and billing. * **Total AI cost and token-level visibility** — break spend down by provider, model, and token type (input, output, cached, reasoning) for any entity and any time window. * **Start from base model pricing** — Flexprice maintains a **public model pricing repository, refreshed daily**, so cost is computed out of the box for popular providers and models with no manual price setup. * **Or customise costing for negotiated pricing** — override the base catalog with your committed or BYOK rates per provider, model, or customer, and apply markup when you resell. * **Set spend alerts and limits, and optionally fund wallets with preloaded credits** — configure `info` → `warning` → `critical` thresholds on balances and usage, and give teams or customers a prepaid credit balance to draw down. See [Alerts and Notifications](/docs/customers/threshold-notifications). * **Model your full org hierarchy** — represent organizations, workspaces, teams, and users with both individual and consolidated limits and rollups, sharing wallets where you need to. See [Customer Hierarchy](/docs/subscriptions/customer-hierarchy). AI Cost Tracking builds directly on Flexprice's core metering primitives. Usage arrives through the same [event ingestion](/docs/event-ingestion/overview) pipeline, is measured with [features and aggregations](/docs/product-catalogue/features/overview), and is governed with [wallets](/docs/wallet/prepaid-and-promotional) and [alerts](/docs/customers/threshold-notifications) — nothing new to learn if you already use Flexprice. ## Architecture At a high level, third-party sources send usage into Flexprice through one of two ingestion paths, and Flexprice turns that usage into cost, governance, and analytics. Flexprice AI Cost Tracking architecture Two ingestion paths cover every source: * **Flexprice Collector** — a lightweight, [Bento](/docs/collectors/overview)-based collector you can run in your own infrastructure. Best for **real-time push** sources (such as LiteLLM webhooks) and for data that must stay inside your network. * **Managed pull** — Flexprice runs scheduled, credential-based pulls for sources reachable by API, SQL, or object storage (such as Langfuse, Databricks, Snowflake, and Bedrock logs). You paste credentials; there is **nothing to deploy**. Either way, every source is normalised to the same standardised AI usage event, so metering, pricing, alerts, and analytics work identically regardless of where usage originated. The next page, [Ingesting AI Usage](/docs/ai-cost-tracking/ingesting-ai-usage), covers this in depth. ### Communication back to your systems Cost tracking is not only inbound. When a budget threshold is crossed, Flexprice can push signals **back** to your systems so limits are actually enforced: * **Webhooks** — every alert state change (`info` → `warning` → `critical`) is delivered as a webhook, so your automation can react however you choose. See [Alerts and Notifications](/docs/customers/threshold-notifications). * **Native enforcement at the gateway** — for gateways that expose programmatic key controls, Flexprice can act on a breach directly: * **LiteLLM** — using your LiteLLM master key, Flexprice can call the proxy's management API to lower or zero a key/team budget, or block a key, for a true hard cutoff. (LiteLLM also emits its own budget alerts to Slack and webhooks independently.) * **OpenRouter** — Flexprice can use the Provisioning API to set a per-key credit limit or disable a key. * **Portkey** — budgets are enforced at the gateway but configured in the Portkey dashboard, so enforcement there is alert-driven rather than API-driven. Start with **soft** alerting (webhooks and notifications) to build confidence, then graduate the same thresholds to **hard** enforcement at the gateway once you trust the numbers. The standardised event, connector types, and per-source mechanics. Configure spend thresholds and webhook delivery. Model orgs, teams, and users with shared and consolidated limits. Fund teams or customers with prepaid credit balances. # Build Credit-based Pricing like Apollo.io Source: https://docs.flexprice.io/docs/boiler-plate/credit-based-pricing Credit-based pricing is a usage-based model where customers prepay for a set number of credits and spend them as they use features, with each action deducting a defined credit amount. ## Overview Apollo.io uses a credit-based pricing model where users spend credits to access contact data (like emails, phone numbers, and exports), with higher plans offering more monthly credits. This guide shows how to build the same model in Flexprice. ## Pricing Breakdown Credit-based pricing breakdown ## 1. Key Chargeable Features in Apollo In this model, credits are deducted when the user does an action (e.g. unlock email, export). In Flexprice you will map each action to a credit cost and debit the wallet. ### A. Contact Data Unlock & Enrichment #### How Credits Are Applied **Prepaid allocation:** When a user subscribes, they receive credits up to the plan limit. You configure this as recurring grants on the plan. Plans can be billed annually with **recurring grants** (e.g. credits per month). * **Professional plan → 48,000 credits/year** (or e.g. 4,000/month as recurring grants) * **Basic → 30,000 credits/year** (or e.g. 2,500/month as recurring grants) **Example:** For a Basic plan billed annually, give 2,500 credits per month via recurring grants. When you create your plan, mirror these as recurring grants. **What you’ll do:** Assign a credit cost to each action and debit that amount from the wallet when the user does it. | Action | Credit Cost | | --------------------- | ------------------- | | Unlock verified email | 1 credit | | Unlock mobile number | 5–10 credits | | Export contact | 1 credit | | API enrichment | 1 credit per record | ### B. Email Sending & AI Outreach Credits #### How Credits Are Applied **Email Sending Model:** Depending on plan, choose one: **daily send limits per mailbox** or **credits per email sent**. When you set up features, create a metered feature (e.g. Emails Sent) and set the credit cost per use. | Action | Credit Cost | | ---------------------------- | ------------------- | | Send 1 email | 1 credit | | AI-generated personalization | 1 additional credit | | Email verification | 1 credit per email | ## 2. Apollo-Style Credit Model ### Key Behaviors Define a credit cost for each action (e.g. 1 for search, 5 for export) in your app or feature config. Let users purchase credits (packages or add-ons) by topping up their wallet. If balance is zero, block the action and prompt the user to buy more credits or upgrade. ### Example: Credit Cost per Action | Action | Credit Cost | | ---------------- | ----------- | | Contact Search | 1 credit | | Contact Export | 5 credits | | Email Enrichment | 2 credits | | Phone Reveal | 10 credits | ### Example: Credit Packages (Prepaid) | Plan | Credits | Price | | ------- | ------- | ----- | | Starter | 1,000 | \$29 | | Growth | 5,000 | \$99 | | Pro | 20,000 | \$299 | **You decide:** Credits can reset monthly, never expire, or expire after N days. Configure this in your plan or billing logic. **To charge overage:** In Flexprice, configure overage on the plan or charge (e.g. 1.00 USD per extra credit). Usage above the included grant is then billed automatically. *** ## 3. When to Use Credit-Based Pricing Use credit-based pricing when each action has a different cost (e.g. 1 credit to unlock an email vs 10 credits to reveal a phone number). Use it when usage varies a lot per customer and a fixed plan doesn’t fit. Use it when you want flexible consumption and optional add-ons or top-ups. *** ## 4. Complete Flow Summary **High level, you will:** Create **metered features** for each action that costs credits. Link each feature to plans via **entitlements**; there you set which features each plan gets and optional free usage (e.g. 50 free, then deduct from credits). See [Linking features to plans](/docs/product-catalogue/features/linking-to-plans). Create a **Plan** with credit packages or usage charges (e.g. annually billed with recurring credit grants per month). Create a **Customer** (with External ID for your system). For **plan-based credits:** Add Subscription → choose the plan. The customer gets the plan’s credit grants and feature access. For **direct purchase only**, skip this and top up the wallet in the next step. *** ## Flexprice Set-up To implement Apollo-style credit-based pricing in Flexprice, follow these steps in order. Navigate to **Flexprice Dashboard** → **Product Catalog** → **Features** Create Features (metered) for credit-based pricing For each action that costs credits (e.g. Contact Export, Email Enrichment): click **Create Feature**, choose **Metered**, and set **Event Name** and **Aggregation**. Later you’ll link these to the plan and set entitlements (e.g. 50 free, then deduct from credits). For more, see [Creating a feature](/docs/product-catalogue/features/create) and [Linking features to plans](/docs/product-catalogue/features/linking-to-plans). Navigate to **Flexprice Dashboard** → **Product Catalog** → **Plans** Add Subscription and credit grants **Do this:** (1) Click **Add** and enter plan name and billing (e.g. Basic, annually). (2) Add charges that provide credits (e.g. recurring credit grant per month) or link metered features with pricing. (3) In **Entitlements**, link features so subscribers get the right access and optional free usage. When a customer is assigned this plan, they receive the credit grants you configured (e.g. monthly). For more, see [Creating a Plan](/docs/product-catalogue/plans/create) and [Linking features to plans](/docs/product-catalogue/features/linking-to-plans). **Do this:** Open the customer → **Add Subscription** → select your plan. **Result:** The customer gets the plan’s credit grants (e.g. monthly) and access to the features you linked to that plan (including any free usage you set). Add Subscription and credit grants For **direct purchase only**, skip this step and top up the wallet in Step 6 (Top Up the Wallet). For more, see [Plans](/docs/product-catalogue/plans/create) and [Linking features to plans](/docs/product-catalogue/features/linking-to-plans). Navigate to **Flexprice Dashboard** → **Customer Management** → **Customers** Create Customer in Flexprice **Enter at least:** Customer Name (e.g. `Flex Corporation`). Add **External ID** if you use it for API lookups. For details, see [Customer Management](/docs/customers/create). Before allowing the user to perform a credit-consuming action, call the **Get Wallet Balance** API (e.g. with customer identifier or wallet ID). If balance is available and sufficient for the action cost, proceed. If not, do not proceed—block the action or prompt the user to top up, then leave. On the customer’s **Wallet** tab, click **Top-up Wallet**. * Choose the credit type that matches your Price Unit (**Custom** → Credits). * Enter the amount in credits (e.g. 1,000). * Complete the top-up (e.g. **Add Credits** or link to payment). **Use this step for:** First-time credit purchase or add-on packs. For more, see [Top-up wallet](/docs/wallet/top-up) and [Auto top-up](/docs/wallet/auto-top-up). Each time a user does an action (search, export, enrichment), do the following in your app or backend: **Check entitlement:** Does this customer’s plan allow this feature? (You set this when [linking features to plans](/docs/product-catalogue/features/linking-to-plans).) If not, block the action. **Look up credit cost:** Use your mapping (e.g. the tables in **Key Chargeable Features in Apollo** above) and get the cost for this action. **Check balance:** Call the Flexprice API or open the dashboard and read the customer's wallet balance. If **balance ≥ cost**, call the Flexprice API to **debit** the wallet by that amount, then perform the action. If **balance \< cost**, return an error or prompt to purchase more credits. **Debit via API:** Call the wallet debit endpoint with the customer identifier (e.g. External ID) and the amount in credits. **Debit via Dashboard:** **Customer** → **Wallet** → **Manual Debit**; enter the amount in credits and reason (e.g. “Contact export – 5 credits”). For more, see [Manual Debit](/docs/wallet/manual-debit). *** ## Next Steps **After setup, do this:** Map actions to credit costs in your application (e.g. 1 credit per search, 5 per export) and call the debit API (or Manual Debit) after each action. **Optional:** Set low-balance alerts; see [Low balance alert](/docs/product-catalogue/features/wallet-balance-alert). Check that invoices show credit top-ups and usage correctly in base currency. Add more plans with credit packages (recurring or one-time) using the same Price Unit; see [Custom pricing use cases](/docs/product-catalogue/custom-pricing/use-cases). For custom pricing concepts and API details, see [Custom Pricing](/docs/product-catalogue/custom-pricing/pricing-unit) and the [API Reference](/api-reference/introduction). # Build Hybrid Pricing like Resend Source: https://docs.flexprice.io/docs/boiler-plate/hybrid-pricing **Hybrid pricing** combines fixed **subscription** fees (recurring base charge) with usage-based metering (pay-per-use overages), plus **entitlements** that gate features and set limits per plan. ## Overview Resend does this with tiers (Free, Pro, Scale, Enterprise): each tier has a recurring fee, usage charges (emails sent or contacts stored), and feature gating (retention, domains, support, analytics). In Flexprice you use one plan with recurring + usage-based charges and features linked via entitlements. ## Pricing Breakdown Resend pricing breakdown ## 1. Key Chargeable Features in Resend Resend charges by **usage** (emails sent or contacts stored) and **gates features by plan** (data retention, domains, support, analytics, dedicated IP). Two tracks run in parallel; this cookbook uses the **Transactional** track for the Flexprice setup. ### A. Transactional Emails **How pricing is applied:** You are charged by **emails sent per month**. Plans scale by volume; unit cost drops at higher tiers. Each tier also defines how many domains you can use, data retention for logs, support channel, and whether dedicated IP is available. **Pricing (volume tiers):** * **Transactional Scale:** 100,000 emails = $90 → $0.90 per 1,000 emails * **Transactional Scale:** 1,000,000 emails/month = $650 → $0.65 per 1,000 emails * **Transactional Pro:** e.g. \$20/mo for 50k emails (common entry point) | Tier | Example volume | Effective rate | | ----- | -------------- | ----------------- | | Pro | 50k emails | \$0.90/1k overage | | Scale | 100k emails | \$0.90/1k | | Scale | 1M emails | \$0.65/1k | Transactional emails are API-triggered (password resets, verification, order confirmations). In Flexprice you model this as a **metered feature** (e.g. Emails Sent) with event name and aggregation. ### B. Marketing Emails **How pricing is applied:** You are charged by **contacts stored** (not by emails sent). Sends can be unlimited; cost scales with contact list size. Tiers control number of audiences, analytics, and dedicated IP (e.g. included on Enterprise for Marketing). | Dimension | Unit | Notes | | ----------- | ----------- | -------------------- | | Contacts | count | Billed by list size | | Emails sent | unlimited\* | \*Within plan/limits | Marketing use cases include newsletters, onboarding campaigns, and promotions. For Flexprice, add a second metered feature (e.g. **Contacts Stored**) with its own event name if you model the Marketing track. *** ## 2. Subscription and Entitlement Model Plan tiers (Free, Pro, Scale, Enterprise) control both **volume/price** and **which features** the customer can use. Features and limits vary by tier: | Feature | Free | Pro | Scale | Enterprise | | ---------------- | ------ | ------ | -------------- | --------------- | | Data retention | 1 day | 3 days | 7 days | Custom | | Domains (Tx) | 1 | 10 | 1,000 | Flexible | | Support | Ticket | Ticket | Slack + Ticket | Priority | | Analytics (Mktg) | No | Yes | Yes | Yes | | Dedicated IP | — | Add-on | Add-on | Included (Mktg) | In Flexprice you control this by **linking features to plans** (entitlements): set included quantity, usage reset, and soft or hard limits for metered features; grant or restrict boolean/static features per plan. For more, see [Linking features to plans](/docs/product-catalogue/features/linking-to-plans). *** ## 3. Overage and Upgrade Logic Resend does **not** automatically charge for excess usage. When you exceed your monthly quota, you are notified and prompted to upgrade; if you repeatedly exceed limits without upgrading, Resend may temporarily pause sending. In Flexprice you can choose **soft limit** (overage allowed, with or without extra charge) or **hard limit** (strict cap). Configure limits and overage per plan when [linking features to plans](/docs/product-catalogue/features/linking-to-plans). *** ## Concepts and Terms * **Subscription:** A customer attached to a plan. The subscription determines billing and which entitlements the customer has. * **Plan:** A set of pricing rules: **recurring charges** (flat fee per billing period) and optionally **usage-based charges** (metered). A plan that has both is **hybrid** in Flexprice. * **Feature:** Something you track or gate—**Metered** (usage, e.g. emails sent), **Boolean** (on/off access), or **Static** (fixed attribute). * **Entitlement:** A feature linked to a plan with access rules: for metered features, included quantity, reset period, and soft or hard limit; for boolean/static, grant or restrict. **Entitlements are free.** For more detail, check-out [Linking features to plans](/docs/product-catalogue/features/linking-to-plans) and [Plans overview](/docs/product-catalogue/plans/overview). ## Complete Flow Summary The end-to-end flow for hybrid (subscription + entitlement) pricing in Flexprice: ```mermaid theme={null} flowchart LR Features --> Plan Plan --> Entitlements Plan --> Subscription Customer --> Subscription Subscription --> Events Events --> Invoices ``` Define the usage dimensions (e.g. Emails Sent) as **metered features** with event names and aggregation. Create a **plan** (e.g. Transactional Pro). Add a **recurring charge** (e.g. \$20/month) and **usage-based charge(s)** for the metered feature(s) (e.g. included 50k emails, then overage or tiered pricing). **Link features to the plan.** Set included quantity, usage reset (e.g. monthly), and soft or hard limit for metered features. Add boolean/static features if you gate capabilities by tier. Create a **customer** (name, External ID) in Customer Management. **Add subscription** for the customer and select the plan. Start date determines billing and when entitlements apply. Send **usage events** that match the metered feature event names. Usage is billed per the plan’s usage-based charges and respects entitlements. Invoices are generated per billing cycle. ## Flexprice Set-up To implement Resend-style hybrid pricing in Flexprice (Transactional track), follow these steps in order. Navigate to **Flexprice Dashboard** → **Product Catalog** → **Features** Create a feature Click **Create Feature** (or **Add**) to create a metered feature. Enter **Name** and **Lookup Key**; the Lookup Key is immutable and unique. **Feature 1: Emails Sent (Transactional)** * **Feature Name**: `Emails Sent` * **Feature Lookup Key**: `emails_sent` (immutable, unique) * **Feature Type**: `Metered` * **Event Name**: e.g. `emails.sent` (must match exactly when sending events) * **Aggregation**: `Count` (e.g. over `Sum` or `emails`) * **Unit**: `email` or `emails` > **Note:** The Event Name must match exactly when you send usage events via API. Save it for later use. For a second track (Marketing), add a feature such as **Contacts Stored** with its own event name and aggregation. This guide uses one metered feature: **Emails Sent**. For more, check-out [Creating a feature](/docs/product-catalogue/features/create). Navigate to **Flexprice Dashboard** → **Product Catalog** → **Plans** Create a plan Click **Add** at the top-right to create a new plan. Enter plan details, then click **Next** to configure charges. **Plan details:** * **Plan Name**: `Pro` * **Lookup Key**: e.g. `plan-pro` (Auto-generated) * **Billing period**: e.g. Monthly **Add hybrid charges:** 1. **Recurring charge:** Add a **recurring** charge (e.g. \$20/month). This is the base subscription fee for the tier. Click **Add** to save. 2. **Usage-based charge:** Add a **usage-based** charge for the metered feature **Emails Sent**. Set pricing (e.g. flat fee per email, or package/tiered with included quantity and overage). Example: first 50,000 emails included, then \$0.2 per 1,000 emails. Click **Add**, then **Save** to finalize the plan. For more, check-out [Creating a Plan](/docs/product-catalogue/plans/create) and [Plans pricing](/docs/product-catalogue/plans/pricing) (recurring and hybrid charges). Open the plan you created → **Entitlements** section. Click **Add** to link features to the plan. Add entitlement to plan **Link the metered feature** (Emails Sent) to the plan: * Set **included quantity** (e.g. 50,000 emails per month) or leave uncapped. * Set **usage reset** (e.g. monthly) so limits reset each billing period. * Choose **soft limit** (overage allowed, with billing per plan) or **hard limit** (strict cap). If you created **boolean or static features** (e.g. Analytics, Slack support), add them to the plan and set grant/restrict so higher tiers get access. Click **Save**. For more, check-out [Linking features to plans](/docs/product-catalogue/features/linking-to-plans). Navigate to **Flexprice Dashboard** → **Customer Management** → **Customers** Click **Add Customer**. Enter **Customer Name** and **External ID** (your system’s identifier; use this in APIs for usage and lookups). **Required:** * **Customer Name**: e.g. `flexprice organisation` * **External ID**: e.g. `cust-flexprice-organisation` Click **Save** to create the customer. For more, check-out [Customer Management](/docs/customers/create). Open the customer you created, then click **Add Subscription**. Add subscription to customer * **Select plan**: Choose **Transactional Pro** (or the plan you created). * **Start date**: Set subscription start (billing and entitlements apply from this date). Click **Save** (or **Add Subscription**). The customer now has the plan’s recurring charge and entitlement to the linked features and usage limits. For more, check-out [Create Subscription](/docs/subscriptions/customers-create-subscription). Send **usage events** to Flexprice with the same **event name** as the metered feature (e.g. `emails.sent`). Usage is aggregated and billed according to the plan’s usage-based charges and respects the entitlements (included quantity, soft/hard limit). * **Verify events:** Use the Event Debugger or usage views to confirm events are received and attributed to the customer. usage Events * **Invoices:** Generated at the end of each billing cycle; they include the recurring fee and usage-based line items. Subscription Invoice For more, check-out [Connecting to Billing](/docs/event-ingestion/connecting-to-billing) and your API reference for sending usage events. ## Next Steps * **Map your app to Flexprice:** Emit usage events that match the metered feature event names (e.g. `emails.sent`) and associate them with the customer (e.g. via External ID). * **Configure overage and limits:** Use [Linking features to plans](/docs/product-catalogue/features/linking-to-plans) to set soft vs hard limits and overage pricing per plan. * **Add more plans:** Create Free, Scale, and Enterprise plans with different recurring fees, usage tiers, and entitlements (e.g. analytics, retention) to mirror Resend’s tiers. For event ingestion and billing flow, check-out [Connecting to Billing](/docs/event-ingestion/connecting-to-billing). For plan and charge configuration, check-out [Creating a Plan](/docs/product-catalogue/plans/create) and [Linking features to plans](/docs/product-catalogue/features/linking-to-plans). # Build Usage-based Pricing like Vapi.ai Source: https://docs.flexprice.io/docs/boiler-plate/usage-based-pricing Vapi is a developer platform for building, testing, and deploying realistic, human-like voice AI agents. ## Overview Vapi uses a modular, usage-based pricing model where you pay for distinct components. ## Pricing Breakdown Feature
Module ## 1. Key Chargeable Features in Vapi * Vapi charges customers primarily for **hosting and orchestration**, not for the underlying AI or telecom providers directly. ### A. Hosting Fee for Call Minutes It charges a **fixed hosting fee per call minute.** **Pricing (Pay As You Go):** * **\$0.05 per minute** *** ### B. Hosting Cost for SMS / Chat Messages It also charges a **fixed hosting fee per message.** **Pricing (Pay As You Go):** * **\$0.005 per message** *** ## 2. Fixed Fees vs Usage-Based Charges Pricing consists of **fixed per-unit fees**, not bundled plans or feature-based tiers. ### Fixed Fees (Platform Hosting Costs) These fees are **fixed per unit of usage**: | Feature | Pricing Model | Cost | | ------------------ | ------------- | ------------- | | Call Hosting | Per minute | \$0.05 / min | | SMS / Chat Hosting | Per message | \$0.005 / msg | These fees remain the same regardless of scale (Pay As You Go). *** ### Usage-Based Scaling | Usage Dimension | Pay As You Go | Enterprise | | ----------------- | --------------------------------- | ------------ | | Call Minutes | Usage based | Custom | | SMS / Chat Volume | Usage based | Volume based | | Call Concurrency | 10 included + \$10 / line / month | Custom | *** ## 3. Call Concurrency Pricing Call concurrency determines **how many simultaneous calls** your system can handle. ### Pay As You Go * **10 concurrent calls included** * **\$10 per additional line per month** ### Enterprise * Custom concurrency limits * Volume-based pricing * SLA-backed infrastructure *** ## 4. Enterprise Pricing Model The **Enterprise plan** operates under an **annual contract** with custom pricing. **Enterprise features include:** * Custom call minute pricing * Volume-based SMS and chat pricing * Priority support and SLAs > Enterprise pricing is negotiated directly with the Vapi sales team. ## Complete Flow Summary The complete subscription flow for Vapi usage-based pricing: 1. Create Features (Call Minutes, SMS/Chat) ↓ 2. Create Plans (Pay As You Go with usage charges) ↓ 3. Create Customer (with External ID) ↓ 4. Create Subscription (link customer to plan) ↓ 5. Send Usage Events (via API) ↓ 6. Verify Events (using Event Debugger) ↓ 7. Billing (automatic at end of billing cycle) ↓ 8. Review Invoices (Customer Info) ## Flexprice Set-up To configure Vapi's usage-based pricing in Flexprice, follow these steps in order: Navigate to **Flexprice Dashboard** → **Product Catalog** → **Features** Click **"Create Feature"** to start creating features. You need to create two metered features: Feature
Module **Feature 1: SMS / Chat Hosting** * **Feature Name**: `SMS / Chat Hosting` * **Feature Lookup\_key**: `sms_chat_hosting` (immutable, unique identifier) * **Feature Type**: `Metered` * **Event Name**: `sms.chat.message` (must match exactly when sending events) * **Aggregation Method**: `Sum` * **Aggregation Field**: `messages` * **Unit**: `message` or `msg` * **Description**: `Vapi hosting cost for SMS and chat messages - covers message delivery, session handling, and conversational state management` > **Note**: The Event Name must match exactly when you send usage events via API. Save these event names for later use. For comprehensive information on creating features, see [Features](/docs/product-catalogue/features/create). Navigate to **Flexprice Dashboard** → **Product Catalog** → **Plans** Click **"Add"** at the top-right to create a new plan. **Create Pay As You Go Plan:** Feature
Module **Step 2.1: Plan Details** * **Plan Name**: `Pay As You Go` * **Lookup Key**: `pay_as_you_go` (immutable, unique identifier) * **Plan Description**: `Usage-based pricing plan with flat fees for call minutes and SMS/chat messages` * Click **Next** to proceed to charge configuration **Step 2.2: Define Usage-Based Charges** **Charge 1: SMS / Chat Hosting** * **Select Metered Feature**: `SMS / Chat Hosting` (sms\_chat\_hosting) * **Billing Currency**: `USD` * **Billing Period**: `Monthly` * **Billing Model**: `Flat Fee` * **Price per Unit**: `0.005` (per message) * **Billing Timing**: `Arrears Billing` (bill at end of cycle) * Click **Add** to save this charge Click **Save** to finalize the Pay As You Go plan. For comprehensive information on creating plans, see [Creating a Plan](/docs/product-catalogue/plans/create). Navigate to **Flexprice Dashboard** → **Customer Management** → **Customers** Click **"Add Customer"** to create a new customer. **Required Fields:** * **Customer Name**: `Acme Corporation` (or your customer's name) * **External ID**: `cust_acme_001` (unique identifier from your system) **Optional Fields:** * **Email**: Customer's email address * **Phone**: Customer's phone number * **Address**: Customer's billing address > **Important**: The External ID is what you'll use when sending usage events. Make sure it matches exactly. Click **Save** to create the customer. For comprehensive information on creating customers, see [Customer Management](/docs/customers/create). Navigate to **Flexprice Dashboard** → **Customer Management** → **Customers** Click on the customer you created to open their details page. Click **"Add Subscription"** button. **Subscription Configuration:** * **Select Plan**: Choose `Pay As You Go` from the dropdown * **Subscription Start Date**: Select the start date for billing (e.g., today's date) * **Subscription End Date**: Leave as "Forever" for ongoing subscriptions, or set a specific end date * **Billing Cycle**: This determines when billing cycles start (usually the start date) The subscription preview will show: * All recurring charges (if any) * Usage-based charges that will be billed based on consumption * Estimated costs for the billing period Click **Add Subscription** to save. The subscription will now appear under the customer's details with status "Active". For comprehensive information on creating subscriptions, see [Create Subscription](/docs/subscriptions/customers-create-subscription). Once a customer has an active subscription, you can start sending usage events to Flexprice via API. **API Endpoint:** ``` POST https://api.cloud.flexprice.io/v1/events ``` **Authentication:** Include your API key in the request header: ``` x-api-key: ``` **Example 1: Send SMS/Chat Message Event** ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/events \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "event_name": "sms.chat.message", "external_customer_id": "cust_acme_001", "properties": { "messages": 1 }, "event_id": "evt_sms_67890", "timestamp": "2025-01-15T10:35:00.000Z", "source": "vapi-platform" }' ``` **Example 2: Bulk Events (Multiple Events at Once)** ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/events/bulk \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "events": [ { "event_name": "call.minutes", "external_customer_id": "cust_acme_001", "properties": { "minutes": 10 }, "source": "vapi-platform" }, { "event_name": "sms.chat.message", "external_customer_id": "cust_acme_001", "properties": { "messages": 3 }, "source": "vapi-platform" } ] }' ``` **Event Payload Fields:** * **event\_name** (required): Must match your feature's Event Name exactly (`call.minutes` or `sms.chat.message`) * **external\_customer\_id** (required): Must match the customer's External ID (`cust_acme_001`) * **properties** (required): Contains the aggregation field value * For Call Minutes: `{"minutes": }` * For SMS/Chat: `{"messages": }` * **event\_id** (optional): Your unique identifier for the event * **timestamp** (optional): ISO 8601 UTC timestamp. If omitted, server time is used * **source** (optional): Origin of the event (e.g., "vapi-platform", "api") **Response:** ```json theme={null} { "event_id": "event_01K389J4M1F1NZG6XP0AMD6J52", "message": "Event accepted for processing" } ``` For comprehensive information on sending events, see [Sending Events](/docs/event-ingestion/sending-events). After sending usage events, verify they are being processed correctly using the Event Debugger. **Navigate to Event Debugger:** 1. Go to **Flexprice Dashboard** → **Usage Tracking** → **Event Debugger** **View Events:** The Event Debugger displays a real-time stream of events with: * **Event ID**: Unique identifier for each event * **Event Name**: The feature being tracked (`call.minutes` or `sms.chat.message`) * **Customer Lookup Key**: Customer's External ID * **Source**: Origin of the event * **Timestamp**: When the event occurred **View Event Details:** Click on any event to view the complete payload: ```json theme={null} { "id": "event_01", "external_customer_id": "cust_acme_001", "event_name": "call.minutes", "timestamp": "2025-01-15T10:30:00.000Z", "properties": { "minutes": 5.5 }, "source": "vapi-platform" } ``` **Filter Events:** Use filters to find specific events: * **Filter by Event Name**: `event_name contains "call.minutes"` * **Filter by Customer**: `external_customer_id contains "cust_acme_001"` * **Filter by Time Range**: Set Start Time and End Time * **Filter by Source**: `source contains "vapi-platform"` **Verify Event Processing:** 1. Check that events appear in the Event Debugger shortly after sending 2. Verify the event payload matches what you sent 3. Confirm the `external_customer_id` matches your customer 4. Ensure `event_name` matches your feature configuration exactly 5. Check that `properties` contain the correct aggregation field values For comprehensive information on event debugging, see [Event Debugger](/docs/event-ingestion/event-debugger) and [Validating Events](/docs/event-ingestion/validating-events). ## Next Steps After completing the setup: 1. **Monitor Usage**: Regularly check the Event Debugger to ensure events are being processed 2. **Review Invoices**: Check upcoming invoices to verify billing calculations 3. **Track Usage**: Use the Query tool to view aggregated usage by customer and feature 4. **Scale**: Add more customers and subscriptions as your business grows For troubleshooting, see [Troubleshooting Events](/docs/event-ingestion/troubleshooting). # Changelog Source: https://docs.flexprice.io/docs/changelog What we’ve been shipping at Flexprice lately ## Service accounts Service accounts are now first-class users — create them, give them a display name, update them, and archive them through dedicated endpoints, with API keys scoped to the owning service account for clean audit trails. * **Update endpoint**: New `PUT /v1/users/{id}` lets you rename a service account without touching its API keys * **Soft-delete endpoint**: New `DELETE /v1/users/{id}` archives a service account — blocked while it still has active API keys to prevent orphaned credentials * **User association on secrets**: API keys now record the `user_id` of the service account that owns them and can be filtered by user and expiry * **`super_admin` role**: New built-in RBAC role with wildcard permissions for tenant-wide administrative access * **Dashboard support**: Manage service accounts end-to-end from the dashboard — create, edit, and revoke from the settings area

## Customer hierarchy safeguards Subscription inheritance now enforces a strict one-parent-per-child rule across both create and modify flows — preventing the same customer from being inherited under two different parents. * **Standalone child rejected**: Attempting to inherit a customer that already has a standalone subscription returns a validation error instead of silently creating conflicting hierarchies * **Different-parent inheritance blocked**: A child already inherited under one parent cannot be re-inherited under a different parent * **Same-parent re-inheritance allowed**: Re-running inheritance for an existing parent–child pair continues to work for adding new subscriptions to the relationship

## Signup metadata User signup now accepts a `metadata` object — attach attribution, referral source, or any custom signup context at account creation time and access it later through the user profile. * **Signup form**: The dashboard signup flow captures structured metadata and forwards it to the auth API * **API field**: The signup request accepts a `metadata` map alongside the existing identity fields
**Other changes** * Event debugger now surfaces an `attributed-to-customer` step in the `meter_usage` pipeline view — making it easier to see which customer an event was attributed to and why * Commitment time bucket editor adds validation and refined configuration controls for setting up multi-window commitments from the dashboard * Subscription status filter expanded to include `DRAFT` and `TRIALING` states alongside published and active filters * Optional display `name` field added to users — set it on create, edit it later, and see it returned in user responses * Wallet credit operations now return `409 Conflict` on duplicate idempotency keys instead of `500 Internal Server Error` * Subscription inheritance no longer admits child customers with parent-type subscriptions, preventing nested parent relationships * New `PUT /v1/users/{id}` endpoint to update a service account's name * New `DELETE /v1/users/{id}` endpoint to soft-delete a service account; blocked while active API keys exist * `metadata` field accepted on the user signup request * `breakdown_bucket` flag added to the meter usage detailed analytics request to opt into per-bucket breakdowns in the response * Secret list filters now support `user_id` and `not_expired_at` for scoping API key queries to a specific service account or expiry window * `name` field added to the user create, update, and response payloads
## Commitment time buckets Windowed commitments can now specify time-of-day billing windows — define multiple time buckets with start and end times to apply different commitment rates during peak and off-peak hours. * **Time bucket editor**: `CommitmentTimeBucketsEditor` component lets you add, configure, and reorder time buckets directly from the `CommitmentConfigDialog` * **Hour and minute precision**: Bucket granularity adapts to the meter's bucket size — hour-only for coarse meters, hour+minute for fine-grained meters * **Validation and normalization**: Buckets are validated for completeness and overlap before saving; normalization strips the minute component when the meter doesn't support it * **Price override storage**: Time bucket configuration is stored on the price override, not as separate line items
## Wallet enhancements Wallets gain two new capabilities: prepaid wallets can be converted to postpaid mode via API, and debit transactions now record which credit transactions were consumed. * **Wallet modification**: New `POST /v1/wallets/{id}/modify` endpoint accepts `modification_type: "prepaid_to_postpaid"` to convert a prepaid wallet to postpaid — original and new wallet state are returned in the response * **Consumed credits tracking**: Debit transactions now store the IDs of all credit transactions consumed during the debit in `metadata.consumed_credit_tx_ids` — enabling full credit audit trails * **Conversion rate applied**: Wallet debit operations now apply the configured conversion rate when calculating the credit amount
## Backdated subscription cancellation Immediate cancellations now accept a `cancel_at` timestamp in the past to record a backdated effective date — useful for correcting cancellation records after the fact. * **Past `cancel_at` on immediate cancellation**: Pass a past timestamp with `cancellation_type: immediate` to set a backdated effective cancellation date * **Future timestamps rejected**: Passing a future `cancel_at` with immediate cancellation returns a validation error directing you to use `scheduled_date` cancellation instead * **Scheduled date unchanged**: Forward-dated cancellations continue to require `cancellation_type: scheduled_date` and reject past timestamps
## `subscription.cancelled` event The `subscription.cancelled` system event is now published whenever a subscription ends — covering immediate cancellation, scheduled-date cancellation, and period-end termination. * **Cascade propagation**: When a parent subscription is cancelled, the event is also published for each inherited child subscription * **Temporal activity support**: `PublishSubscriptionEvent` is exposed on the subscription service interface so Temporal billing-period activities can fire lifecycle events
## Paddle inbound invoice sync Paddle webhook processing now handles inbound invoice state changes — invoice updates arriving from Paddle are reflected back into Flexprice without waiting for the next outbound sync cycle. * **Inbound update handling**: `PullAndUpdateInvoice` is wired as the inbound path in Paddle's sync service; the sync type toggle enables or disables inbound processing per connection * **Child subscription guard**: Paddle subscription sync workflow no longer fires on child subscriptions to prevent duplicate checkout URL generation
**Other changes** * Go runtime upgraded to 1.25.0 * Meter usage consumer is now lazy — initialized on first use rather than at startup, reducing cold-start pressure * Subscription billing scheduler interval reduced from 15 minutes to 2 minutes; stale webhook scheduler avoids system events older than 48 hours and prioritizes newly scheduled events * Environment management: new `add-environment` script creates an environment for an existing tenant and wires in-memory cache * Invoice pull sync cron configured; \$0 invoices are skipped from outbound sync * Meter usage queries now support `group_by` on properties * Helm chart: `onboarding_events` config block now rendered so the Kafka topic value is non-empty * Paddle webhook handler now returns an error response on processing failure so Paddle retries the delivery * Tax rate filter in subscription creation now excludes archived rates — only published rates are shown (frontend) * Cancelled subscription meter usage benchmark fixed to exclude post-cancellation usage windows * Meter usage `group_by feature_id` aggregation corrected * `POST /v1/wallets/{id}/modify` endpoint added — accepts `modification_type` for wallet mode conversion * Swagger documentation updated with new integration sync endpoints
## OTel tracing migration Tracing is now OpenTelemetry-native across the entire backend — all span helpers (`StartDBSpan`, `StartClickHouseSpan`, `StartRepositorySpan`, `StartMonitoringSpan`) keep their call-site API but are backed by `go.opentelemetry.io/otel`. Traces route to SigNoz or any OTLP backend; Sentry is retained only for error capture. * **OTel spans**: All existing span helpers now emit OTel spans — no call-site changes required * **SigNoz routing**: The unified `otel:` config block exposes per-signal endpoints and headers so traces and logs can target different backends independently * **Trace-log correlation**: `logger.WithContext` injects `trace_id` and `span_id` into every log line for cross-signal correlation * **Temporal interceptor**: Rewritten to use the OTel tracer for activity spans; Sentry scope binding preserved for error attribution * **Config migration**: Legacy `logging.otel_*` keys remain valid with a deprecation warning; the new `otel:` block is the canonical path
## White-label SDK pipeline A new CI pipeline automates generation and publication of white-label SDKs for partner deployments — covering Go, TypeScript, Python, and MCP with per-partner branding applied at build time. * **`generate-wl-sdks.yml`**: GitHub Actions workflow triggers on `sdk-v*` tags and manual dispatch; builds all four SDK flavors and publishes to partner repositories * **Config templates**: Per-partner `gen-config` files parameterized with `WL_*` environment variables for name, module path, and author metadata * **Branding scripts**: `apply-wl-custom-branding.sh` replaces all Flexprice identifiers in source and docs; `verify-sdk-builds.sh` confirms each SDK compiles after substitution * **Template drift detection**: `check-wl-templates` Makefile target and a CI check fail if the canonical SDK drifts from the white-label template baseline
## GCP Managed Kafka Kafka now supports SASL/OAUTHBEARER authentication for GCP Managed Kafka — no username or password required. Workload Identity handles token acquisition on GKE; Application Default Credentials are used locally. * **OAUTHBEARER mechanism**: Set `saslMechanism: OAUTHBEARER` in the Kafka config; tokens are sourced via `google.DefaultTokenSource` and cached with `oauth2.ReuseTokenSource` to avoid metadata server round-trips on each broker connect * **Existing SCRAM/PLAIN unchanged**: Sarama config output is byte-identical for existing configurations — no migration required
## Invoice pull sync Integrations now support a `pull` sync method — trigger a sync with `method: "pull"` to fetch and update an invoice's current status directly from the payment provider. * **`PullAndUpdateInvoice`**: Paddle fetches the latest invoice state and applies it back to Flexprice; all other integrations return a clear unsupported error * **Factory wiring**: `IntegrationFactory.SetServices` injects `PaymentService` and `InvoiceService` post-construction to break the service dependency cycle
**Other changes** * Meter usage pipeline: wallet balance, cost analytics, and usage export now query the meter usage store — reducing direct ClickHouse event queries in these paths * User model gains a JSONB `metadata` field with full CRUD support via the user update API * Subscription metadata is now persisted on subscription create and update * JWT middleware now extracts `environment_id` from JWT claims before falling back to the request header * Trial subscription end dates can now be modified, and trialing subscriptions can be cancelled with a backdated `cancel_at` effective date * Revenue dashboard now shows a currency filter populated from available invoice summaries (frontend) * Price sync: parent price ID now sends empty string instead of nil, preventing null dereference during plan-change price linking * White-label branding scripts hardened — `sed` substitutions properly quoted; standard builds guarded against accidental branding replacement * New integration sync service exposes `method: "pull"` and `method: "push"` for on-demand provider sync * Subscription create and response DTOs now include the `metadata` field * User update API now accepts and returns `metadata` key-value pairs
## Whop integration Flexprice invoices can now be synced to Whop — creating hosted invoices or automatically charging customers with a saved payment method, with two-way reconciliation via Whop webhooks. * **Invoice sync**: Finalized Flexprice invoices are pushed to Whop as one-time invoices, with checkout URLs stored back on the invoice metadata for customer-facing payment links * **Automatic charge collection**: When a customer has a saved Whop payment method, invoices are charged automatically instead of sending a hosted checkout link * **Two-way payment reconciliation**: The `invoice.paid` webhook marks the corresponding Flexprice invoice as paid; the `payment.succeeded` webhook creates a customer→member mapping that enables automatic charging on future invoices * **Product auto-provisioning**: A Whop product is created automatically if one isn't already configured on the connection, with the product ID persisted back to the connection config * **Dashboard UI**: Connect and configure Whop from the integrations page — set API key, company ID, and enable invoice sync; copy your unique webhook URL directly from the drawer
## Paddle subscription sync Paddle-connected subscriptions now trigger a Temporal sync workflow that bootstraps a Paddle subscription for card capture — enabling the checkout URL flow without requiring a paid transaction. * **Bootstrap workflow**: `PaddleSubscriptionSyncWorkflow` creates a \$0 Paddle transaction at subscription creation, stores the checkout URL in subscription metadata, and syncs all line item prices to Paddle catalog products * **`subscription.activated` webhook**: Paddle's activation webhook is now handled — Flexprice activates subscriptions in an incomplete state when Paddle signals the subscription as active * **Customer metadata sync**: `paddle_customer_id` is written to the Flexprice customer metadata record on first sync, enabling lookup without an additional API call * **Pending invoice resync**: After a `subscription.activated` event, any pending Flexprice invoices are resynced via `PaddleInvoiceSyncWorkflow` to ensure payment status is up to date
## Wallet auto-topup invoice deduplication Auto-topup now guards against duplicate invoices — a pending unpaid topup invoice blocks a new topup from being triggered, and the alert re-fires automatically once the existing invoice is resolved. * **`WALLET_AUTO_TOPUP` billing reason**: Invoices created by auto-topup are now tagged with a dedicated billing reason, enabling accurate filtering and deduplication * **Pending invoice guard**: Before triggering a new topup in invoiced mode, `hasPendingAutoTopupInvoice` checks for an existing finalized, unpaid auto-topup invoice — and skips if one is found * **Re-trigger on payment**: When a purchased credit payment completes, the wallet balance alert is re-published so `triggerAutoTopup` can fire a fresh invoice for the next topup cycle
## Subscription plan price out-of-sync flag The subscription v2 API response now includes a `plan_prices_out_of_sync` flag indicating whether the subscription's line items are behind the plan's current price configuration. * **Out-of-sync detection**: Compares the subscription's `synced_price_sequence` against the plan's current max sequence — returns `true` when plan-price changes have not yet been reconciled into the subscription's line items
**Other changes** * Zoho line item descriptions now include the child customer name for clearer invoice attribution in multi-tenant setups * Zoho customer fetch logic moved from billing service to Zoho service for better separation of concerns * Zoho customer lookup now filters by `PUBLISHED` status to exclude inactive records * Price sync creates new subscriptions using the latest price sequence, avoiding stale sequence issues after plan updates * Paddle webhook signature verification hardened to handle both snake\_case and camelCase event payload formats * Paddle invoice resync after `subscription.activated` replaced goroutine+sleep with `PaddleInvoiceSyncWorkflow` for reliable, retryable execution * Paddle checkout URL now falls back to connection config when Paddle omits it from the transaction response * Paddle `ListTransactions` filtered by `origin=subscription_charge` to exclude unrelated transactions * Entity integration mapping update now merges metadata instead of replacing it, preserving existing fields on update * Wallet auto-topup invoice guard payment status filter corrected to include failed and initiated statuses alongside processing * Meter usage window size points aggregation corrected * Meter usage zero-events line item commitment now handled correctly without producing incorrect charges * New `POST /webhooks/whop/{tenant_id}/{env_id}` endpoint for inbound Whop webhook events — handles `invoice.paid` and `payment.succeeded` event types * `plan_prices_out_of_sync` boolean field added to the subscription v2 response
## Auto invoice threshold billing Set `auto_invoice_threshold` on any usage-based subscription and Flexprice will automatically generate a mid-period invoice whenever accumulated usage charges cross that amount — no manual intervention required. * **`auto_invoice_threshold` field**: Currency-denominated decimal on subscription creation and modification; must be zero or greater; rejected for inherited subscriptions * **Threshold billing workflow**: `AutoInvoiceThresholdBillingWorkflow` runs on a 5-minute Temporal schedule, scans qualifying subscriptions, and fires mid-period invoices tagged with the `THRESHOLD_BILLING` billing reason * **Validation guards**: Only valid for standalone subscriptions attached to usage-based plans; the API rejects threshold configuration on inherited or delegated-invoicing subscriptions * **Dashboard support**: Set `auto_invoice_threshold` during subscription creation; the current threshold value is shown on the subscription detail page
## White-label dashboard & localization The Flexprice dashboard now supports full white-label customization and RTL localization — deploy it under your own brand with your logo, colors, and language without code changes. * **White-label branding**: Configure logo, tagline, landing page colors, and auth page layout per deployment via config — no frontend code changes required * **RTL layout**: The entire dashboard adapts to right-to-left locales — sidebar, drawers, breadcrumbs, tables, and all directional elements reorient automatically * **Locale switching**: Users can switch their display language at runtime; the selection persists across sessions and the layout direction updates immediately
## Zoho Books item price sync Flexprice prices are now synced to Zoho Books as catalog items and item prices before invoice sync, ensuring Zoho always reflects the correct pricing for every line item. * **`ItemSyncService`**: Normalizes and upserts item prices in Zoho Books' format before attaching them to invoice lines — eliminates mismatches between Flexprice prices and Zoho catalog entries * **`TaxService`**: Dedicated Zoho tax service injected into the invoice sync pipeline for structured tax handling * **OAuth scopes**: Zoho OAuth scopes expanded with `ZohoBooks.settings.READ` and item management permissions for full catalog access
## Helm chart v1.0.0 GA The Flexprice Kubernetes Helm chart reaches v1.0.0 GA with production defaults for high availability, data protection, and frontend scalability, and is now published to GHCR. * **Frontend as first-class workload**: HPA and PDB added for the frontend deployment; multi-AZ topology spread constraints enabled by default for all workloads * **Data protection**: In-cluster Postgres, Redis, Kafka, and ClickHouse StatefulSets ship with keep annotations on by default — data survives chart uninstall * **Redis authentication**: Readiness probe now authenticates via `REDISCLI_AUTH` for clusters with password auth enabled * **GHCR publishing**: Chart published to GitHub Container Registry on `chart-v*` tags and manual workflow dispatch
**Other changes** * Redis client updated to use `ClusterClient` for improved connection management and topology awareness * Price sync now initializes with the latest price sequence to prevent stale sequence issues after plan updates * Invoice `issue_date` now takes priority over `finalized_at` when computing the PDF issuance date * Error codes throughout the codebase replaced with typed `ErrorCode` constants for improved type safety * Commitment true-up: new `GetDistinctCustomerIDsWithCommitmentTrueUp` query for targeted true-up processing * `monthsBetween` now correctly handles end-of-month boundary dates (e.g., Jan 31 → Feb 28) * Nil check added for `ExportMetadataFields` in scheduled task validation to prevent panics * Voided and deleted invoices excluded from invoice idempotency checks * `auto_invoice_threshold` field added to subscription create and modify DTOs * `issue_date` field added to invoice create and update requests — overrides the user-facing invoice date
## Grouped invoicing A new `grouped_invoicing` subscription type lets a parent subscription collect usage from multiple child subscriptions and consolidate them into a single clubbed invoice on the parent's billing cycle. * **`grouped_invoicing` type**: New subscription type alongside `STANDALONE`, `PARENT`, `INHERITED`, and `DELEGATED_INVOICING` — the parent owns billing and the children contribute usage * **Bulk conversion at creation**: Pass `subscriptions_ids_for_grouped_invoicing` in `SubscriptionInheritanceConfig` to convert existing standalone subscriptions to `grouped_invoicing` children under a new parent in a single API call * **Runtime membership management**: Add or remove grouped invoicing members via the subscription modification API using `grouped_invoicing_add` and `grouped_invoicing_remove` change types * **Clubbed invoice preview**: The parent subscription invoice preview endpoint returns a merged view of all child line items, letting you inspect consolidated charges before finalization * **Standalone promotion**: An existing standalone subscription can be promoted to a grouped invoicing parent without data loss
## Trial start invoice When a subscription activates into a trial, Flexprice now automatically creates a \$0 invoice — enabling card capture via the payment processor's checkout flow without charging the customer. * **`SUBSCRIPTION_TRIAL_START` billing reason**: New billing reason triggers a \$0 invoice at trial activation; billing period spans `trial_start` → `trial_end` * **`ZeroOutAmounts()`**: Zeros all monetary amounts on the invoice request while preserving line item structure — used exclusively for trial-start invoices * **Collection-method-aware payment status**: Manual collection marks the trial invoice as PAID immediately; automatic collection generates a checkout URL for card capture * **`SKIPPED` invoice status**: Zero-dollar invoices that require no payment action are marked `SKIPPED` — no invoice number assigned, no finalization, no vendor sync
**Other changes** * Usage analytics export now includes customer name alongside external customer ID in report output * `AppPrefetcher` component prefetches queries on active environment load; `WebhookDashboard` now lazy-loaded for faster initial render * Export drawer supports dynamic metadata field columns with per-field validation * Environment display name can now be updated from the dashboard settings * Invoice line item table: null amounts handled gracefully; filtered data sorted correctly * `mergeAnalyticsData`: fixed nil-check base selection and `PriceResponses` merge from child accounts * Sum-with-multiplier precision corrected for event values with more than 6 decimal digits * Inherited subscription guard restored on nil-inheritance create path to prevent invalid subscription states * `grouped_invoicing_add` and `grouped_invoicing_remove` change types added to the subscription modification API * `subscriptions_ids_for_grouped_invoicing` added to subscription creation DTO under `SubscriptionInheritanceConfig` * `SUBSCRIPTION_TRIAL_START` billing reason added to the invoice billing reason taxonomy
## Scheduled subscription cancellation Subscriptions can now be cancelled at a specific future date — set `cancel_at` on the cancel request and the subscription stays active until that date, then terminates automatically. * **`cancel_at` parameter**: Pass a future timestamp to the cancel endpoint to schedule termination at a precise point in time * **Validation**: The API enforces that `cancel_at` must be strictly in the future, returning a clear error if the date has already passed * **Existing modes preserved**: Immediate and end-of-period cancellation continue to work unchanged alongside scheduled cancellation
## Metadata filtering Customers and plans can now be filtered by arbitrary metadata key-value pairs — useful for multi-tenant setups, account segmentation, and custom tagging. * **JSONB GIN indexes**: Metadata columns are now indexed with PostgreSQL GIN indexes for performant key-value lookups at scale * **Customer and plan APIs**: Both the customer list and plan list endpoints accept `metadata` key-value filters via a shared `MetadataFilter` struct * **Dashboard query builder**: The filter popover now supports metadata key-value pair inputs with a dedicated UI component
## Subscription line item search A new search endpoint lets you query subscription line items across subscriptions with full filtering and pagination support. * **Search endpoint**: `POST /v1/subscriptions/lineitems/search` accepts a `SubscriptionLineItemFilter` body with status, price, and date filters * **Pagination**: Results are paginated and sortable — no need to fetch the entire subscription to inspect individual line items * **Dashboard UI**: The subscription edit charges section now uses this endpoint for filtering and browsing line items directly
## Usage analytics export Usage analytics data can now be exported to CSV from both the API and the dashboard — covering per-customer, per-meter breakdowns for any billing period. * **Export API**: New endpoint returns a CSV of usage analytics including external customer IDs, meter breakdowns, and metadata fields * **Metadata columns**: Credit usage reports now include additional metadata fields configured on the export, with nil-safe handling for missing external IDs * **Dashboard UI**: Usage analytics pages now expose a download button for CSV exports
## Webhook retry job configuration The Temporal-based stale webhook retry job is now fully configurable via server config — enabling per-deployment tuning without code changes. * **Kill switch**: Set `enabled: false` to disable the retry job entirely for a deployment * **Rate limiting**: `rate_limit` (default: 5/s) caps delivery throughput within each cron run using a token-bucket limiter * **Tenant exclusions**: `excluded_tenants` skips specific tenant IDs from retry processing * **Event-type allowlist**: `allowed_event_types` restricts retries to specific webhook event names — empty list retries all types * **Max attempts**: `max_attempts` (default: 5) replaces the hardcoded failure-count threshold in the retry query
**Other changes** * Inherited subscription usage now includes child account events in the usage pipeline — parent subscriptions aggregate usage from all attached child customers * Billing service methods refactored to accept structured DTO inputs with validation, replacing positional argument chains * Subscription billing schedule decommissioned and replaced with the unified `SubscriptionBilling` Temporal schedule * Environment name can now be updated from the settings page * System event domain repository added for structured event querying in the webhook pipeline * Analytics merge now correctly aggregates `PriceResponses` from child accounts when computing parent subscription usage * Metadata filter nil-check corrected in in-memory store to use map lookup instead of index-0 access * Customer ID filtering optimized in `GetUsage` and `matchesBaseFilters` for the in-memory event store * Subscription future date restriction removed — subscriptions can now be created with a `start_date` in the future without a validation error * New `POST /v1/subscriptions/lineitems/search` endpoint for filtering and paginating subscription line items * `cancel_at` parameter added to `CancelSubscriptionRequest` for scheduled future cancellation * Customer list API now accepts `metadata` key-value filters via `MetadataFilter` in the request body * Usage analytics export endpoint added with support for external customer ID resolution and partial-count logging
## Subscription trial periods Subscriptions can now be created with a trial period — pass `trial_period_days` on creation and Flexprice automatically sets the trial window, then transitions to paid billing when the trial ends. * **`trial_period_days` on create**: Pass a non-negative integer to set the trial length; if omitted, the value is inherited from the plan's recurring fixed prices (must be uniform across prices) * **`SUBSCRIPTION_TRIAL_START`**: When a **`trialing`** subscription is finalized on create (non-draft), Flexprice emits a **`$0`** invoice with **`SUBSCRIPTION_TRIAL_START`** — preview line items for the trial window; real charges still begin with **`SUBSCRIPTION_TRIAL_END`** * **Temporal trial end workflow**: A Temporal workflow handles trial expiry — updates subscription status, computes the first invoice, and activates billing at the correct period boundary * **Inherited subscription cascading**: Trial end processing propagates to child subscriptions when the parent trial expires * **Validation**: `trial_period_days` and explicit `trial_start`/`trial_end` are mutually exclusive — the API returns a clear error if both are provided
## Temporal-native scheduling Cron jobs are now managed as Temporal schedules — replacing the legacy cron HTTP endpoints with durable, observable, and pauseable Temporal workflows. * **Temporal schedules**: All recurring billing jobs are now registered as Temporal schedules at worker startup * **Legacy endpoints removed**: The deprecated cron trigger HTTP endpoints have been removed from the API surface * **Subscription billing schedule**: The `SubscriptionBillingPeriods` legacy schedule is replaced by the unified `SubscriptionBilling` Temporal schedule
## Add-on lifecycle controls Add-on subscriptions now support fine-grained lifecycle configuration — set a custom billing cadence, proration behavior, and effective end date when attaching an add-on to a subscription. * **Billing cadence**: Choose the billing frequency for the add-on independently of the base subscription cadence * **Proration behavior**: Select whether mid-cycle add-on changes are prorated or charged at full price * **Effective end date**: Set an explicit termination date for the add-on without cancelling the base subscription * **Dashboard UI**: The add-on dialog now exposes these controls under an advanced options section
**Other changes** * Future subscription start dates are now allowed — the restriction requiring `start_date` to be in the past or present has been removed * Invoice pagination added to the customer invoice tab — large invoice lists no longer load all at once * Add-on subscription query cache is invalidated on add or cancel, keeping the subscription detail page in sync * Event names are now trimmed of leading/trailing whitespace at ingestion and query time for consistent matching * Subscription pause and resume endpoints removed — use cancellation or trial periods instead * Billing anchor adjusted for anniversary-cycle subscriptions to prevent unintended prorated charges on the first invoice * Date handling corrected for add-on line items when the subscription entity type resolves effective dates * Inherited subscription invoicing customer logic corrected — child subscriptions now invoice to the correct parent customer * `trial_period_days` parameter added to `POST /v1/subscriptions` — sets the trial window at subscription creation * `proration_behavior` field added to subscription line item create and modify requests * Pause and resume subscription endpoints removed from the API
## Billing anchor Anniversary-cycle subscriptions now support a custom billing anchor date — override the default subscription start date to align billing renewals to a specific day of the month. * **`billing_anchor` parameter**: Pass a timestamp on subscription creation when `billing_cycle` is `anniversary` to pin the billing renewal date to that day * **Validation**: The API enforces that `billing_anchor` is only accepted for anniversary billing cycles — passing it with other cycle types returns a validation error * **Next billing date**: The first billing period end is computed from the anchor date, so proration on the initial period reflects the actual anchor offset
## Backdated credit grant catch-up Credit grants now retroactively apply to subscriptions created after the grant's start date — a catch-up loop issues the missed credit grant applications for all elapsed billing periods. * **Catch-up loop**: When a subscription is created and a credit grant already has elapsed periods, the system issues one `CreditGrantApplication` per missed period in sequence * **Inherited subscriptions**: The catch-up logic mirrors across inherited child subscriptions, keeping credit entitlements consistent across the parent-child billing hierarchy * **EE parity**: The enterprise edition credit grant service runs the same catch-up path, ensuring consistent behavior across deployment tiers
## Invoice downloads Invoices can now be downloaded as CSV or PDF directly from the customer portal and the customer invoice tab in the dashboard. * **Format picker**: A dialog lets users choose between PDF and CSV before initiating the download * **Customer portal**: The invoice download button in the customer-facing portal now surfaces both formats with distinct icons * **Sorting**: Customer invoices can be sorted by billing period start date, making it easier to locate invoices for a specific period
## Webhook manual retry Failed webhook deliveries can now be retried manually via a new retry endpoint — useful for re-sending events after a destination outage without waiting for the automatic retry job. * **Retry endpoint**: `POST /v1/webhooks/{id}/retry` re-queues a failed webhook event for immediate redelivery * **Svix and native support**: The retry logic works across both the Svix-managed and native webhook delivery backends
**Other changes** * OTel trace attributes now include a `region` field for region-aware observability in multi-region deployments * Environment list API now returns only `PUBLISHED` environments by default * Stale branch and stale PR GitHub Actions added to automate repository hygiene — branches older than 2 months and PRs idle for 15 days are flagged automatically * Wallet top-up conversion rate display improved — conversion rates are now shown clearly in the wallet UI * Credit grant tab on the plan detail page now filters to `PUBLISHED` grants only * Stripe invoice `due_date` is now always set when using `send_invoice` collection mode — previously missing in some flows * Webhook publisher nil-guard added for `systemEventRepo` to prevent panics on deployments without the system event store wired * Coupon percentage type now correctly excludes currency from the payload — fixing validation errors on currency-agnostic percentage coupons * Invoice nil pointer dereference in `IsFinalizationDue` resolved * `POST /v1/webhooks/{id}/retry` endpoint added for manual webhook redelivery * `billing_anchor` parameter added to `POST /v1/subscriptions` for anniversary billing cycle anchor date override
## Environment cloning Duplicate an entire environment — all published features and plans are cloned into a new target environment via a Temporal workflow, preserving entitlement relationships and configuration. * **Temporal workflow**: `EnvironmentCloneWorkflow` runs two sequential activities — features are cloned first (so plan entitlements resolve correctly), then plans are cloned with ID remapping * **Cross-env ID mapping**: Source entity IDs are mapped to their cloned counterparts in the target environment, keeping references intact * **Dashboard support**: Environment cloning is accessible from the environment management UI — select a source and target environment to kick off the clone
## One-time charges Prices can now be configured as one-time charges using `billing_period: ONETIME` — a cleaner model that separates cadence from charge type and integrates directly into the subscription billing pipeline. * **`BILLING_PERIOD_ONETIME`**: One-time charges are now identified by `billing_period` rather than `billing_cadence`, with the cadence defaulting to `RECURRING` — a data migration normalizes existing records * **Auto-inclusion**: Subscriptions automatically include `ONETIME` prices during billing cycle evaluation — no explicit line item attachment required * **Period ordering**: The period comparison utilities now handle `ONETIME` correctly for sorting and validation alongside standard billing periods * **Validation**: The minimum billing period count check no longer applies to one-time prices
## AI-assisted pricing setup - Prompt to Plan Prompt to Plan introduces a new way to set up billing in Flexprice. Instead of manually configuring plans, prices, meters, and entitlements, you can describe your pricing model in plain English and generate a working configuration. * **Chat-based setup**: Describe subscription, usage-based, or hybrid pricing models conversationally and get a complete configuration back * **Full model support**: The workflow covers metered features with overages and limits, credit-based pricing with grants and expiration, add-ons, and entitlements * **Preview before publishing**: Review the generated setup before pushing it live — nothing is saved until you confirm * **Templates**: Pre-built templates based on real-world pricing models are available as starting points and can be customized and deployed directly
## Zoho Books integration Connect Flexprice to Zoho Books for automated invoice synchronization — invoices finalized in Flexprice are pushed to Zoho Books via a Temporal workflow with OAuth-backed authentication. * **OAuth connection**: Authenticate with Zoho Books via the integrations page — the OAuth flow stores connection metadata and tokens for the tenant * **Invoice sync workflow**: `ZohoBooksInvoiceSyncWorkflow` runs on invoice finalization and pushes the invoice payload to Zoho Books * **Connection management**: The integrations UI shows connection status and supports re-authentication
## Mid-cycle subscription modification Subscriptions can now be modified mid-cycle — change line item quantities with a full proration preview before committing. * **Proration preview**: Call the modification preview endpoint to see the prorated credit and debit line items before applying the change * **Quantity change dialog**: The dashboard surfaces a quantity modification dialog on subscription line items with a summary of the expected invoice impact * **Proration moved outside transaction**: The proration calculation is now performed outside the database transaction to avoid long-held locks during wallet lookups
**Other changes** * Meter usage pipeline added — a dedicated pipeline processes feature usage events and maps them to meter usage records for downstream billing * Feature lookup keys can now be auto-generated from the feature name — reducing manual input when creating features * Customer invoice summary now includes invoices billed to parent customers, giving a complete view of charges for child accounts * Invoice table in the customer view now shows a `Billing Entity` column and a `Subscription Customer` column for parent-child billing scenarios * Usage benchmark service added with ClickHouse-backed storage for tracking event processing latency * Subscription `BillingCadence` now defaults to `RECURRING` in `CreateSubscriptionRequest` validation — prevents misconfigured subscriptions when cadence is omitted * Meter usage list lookup now cached in-process per event name — reduces ClickHouse round-trips for high-volume ingestion * Unique hash generation in meter usage fixed to correctly deduplicate cache entries * New `POST /v1/ai/pricing/parse` endpoint powers the Prompt to Plan chat workflow — accepts pricing descriptions and returns structured plan, price, meter, and entitlement configurations * New `POST /v1/subscriptions/{id}/modify/preview` endpoint returns a prorated invoice preview before applying a subscription modification * Environment clone endpoint added to trigger the `EnvironmentCloneWorkflow` for a source/target environment pair
## Inherited subscriptions Subscriptions now support an inheritance model — a parent subscription can propagate charges, cancellations, and billing configuration to child subscriptions attached to the same customer. * **Subscription types**: Subscriptions are now typed as `STANDALONE`, `PARENT`, or `INHERITED` — controlling how billing is routed and how cancellations cascade * **Inherited subscription creation**: Create subscriptions that inherit from a parent, with automatic customer resolution and unified code paths for create and modify operations * **Cascade cancellation**: Cancelling a parent subscription automatically propagates cancellation to all inherited child subscriptions * **Validation guards**: The API prevents creating standalone or parent subscriptions on customers that already have an inherited subscription, avoiding conflicting billing hierarchies * **Dashboard support**: The subscription edit page now shows an inherited subscriptions section for managing child subscriptions from the parent view
## SDK v2.1 The Flexprice Go, Python, and TypeScript SDKs have been updated to v2.1, with major developer experience improvements across error handling, idempotency, and pagination. * **Error utilities**: New `errorutils` package provides typed helpers — `IsNotFound`, `IsValidation`, `IsConflict` — for clean error handling without string matching * **Per-request idempotency**: `WithIdempotencyKey` option lets you attach an idempotency key to any mutating request without modifying global config * **Auto-pagination**: List endpoints now support automatic pagination — iterate over all results without managing page tokens manually * **Retry configuration**: Read-only `POST` operations (like usage queries and previews) now retry automatically; retry settings are configurable globally or per-request * **SDK examples**: Comprehensive end-to-end examples added for Go, TypeScript, and Python covering the full subscription and billing lifecycle

## Invoice internal preview Preview invoice charges before finalization with a new internal preview endpoint — useful for validating billing logic before committing to an invoice. * **Preview endpoint**: New `POST /v1/invoices/internal/preview` computes a draft invoice view without persisting any state — returns line items, totals, and applied discounts * **PDF force regeneration**: The invoice PDF URL endpoint now accepts a `force_generate` parameter to regenerate PDFs on demand, bypassing the cached version
## Usage charge arrear constraint Usage-based charges are now constrained to arrear billing — the API enforces that usage charges cannot be configured for advance billing. * **Arrear-only enforcement**: Prices with usage charge models are validated at creation and update time to ensure they are always billed in arrear, preventing misconfigured advance billing setups
## Entitlement usage reset period Plan entitlements can now be configured with a usage reset period directly from the plan builder. * **Reset period UX**: The plan creation and edit flow now includes a reset period selector for entitlements — configure how frequently usage limits reset (daily, weekly, monthly, etc.) without leaving the dashboard
**Other changes** * Customer list now defaults to filtering by `PUBLISHED` status — inactive customers are hidden by default with the ability to clear the filter * Invoice finalization delay reduced from 5 days to 2 hours — integrations and async workflows have a shorter window before invoices are sealed * Temporal invoice workflows now start with a configurable delay, giving downstream systems time to react before processing begins * System events enhanced with entity type and entity ID fields, and a new `event_name` column for structured audit log queries * OTel log level filtering now respected in the pipeline — debug-level noise no longer propagates to exporters configured for higher levels * Temporal worker concurrency defaults reduced to prevent out-of-memory kills under high load * API error messages extracted and displayed more accurately throughout the dashboard — network and validation errors now surface actionable text instead of raw response objects * Revenue dashboard date range filter now includes UTC-inclusive period boundaries — data at period edges was previously excluded * ClickHouse aggregator queries no longer use the `FINAL` keyword — removes read inconsistencies on tables with frequent merges * Invoice computation now correctly identifies inherited subscriptions and skips re-computation when appropriate * Credit purchased invoice payment status flow corrected — status transitions now follow the expected finalization sequence * Subscription invoice draft idempotency key now includes `billing_reason` — prevents duplicate drafts across different billing triggers within the same period * Duplicate draft event publishing removed from `CreateInvoice` — downstream consumers no longer receive redundant `invoice.drafted` events * Invoice PDF generation now handles nil line items without panicking * New `POST /v1/invoices/internal/preview` endpoint for computing a preview invoice without persisting state * Subscription type field (`STANDALONE`, `PARENT`, `INHERITED`) added to the subscriptions API with filtering support * New endpoint for modifying subscription inheritance — add or remove inherited subscriptions from a parent * `force_generate` parameter added to the invoice PDF URL endpoint for on-demand PDF regeneration * Go SDK v2.1.0 and v2.1.1 released with error utilities, idempotency key support, auto-pagination, and configurable retry strategy * TypeScript SDK updated to use `CustomerFromLookup` type for customer resolution responses
## Revenue dashboard Visualize revenue across your customer base with a new analytics dashboard — filterable by time period with per-customer breakdowns. * **Time-series graphs**: View total revenue and usage trends over configurable date ranges (this month, this quarter, this year, and prior-period comparisons) * **Per-customer breakdown**: Each customer row shows total revenue, usage-based revenue, and fixed revenue with drill-down links to customer details * **Flexible filtering**: Filter by custom date ranges or preset periods — results are sorted by total revenue descending for quick identification of top accounts
## Invoice compute workflow Trigger invoice computation on demand via API — synchronously for immediate results or asynchronously via Temporal for large invoices. * **Sync and async modes**: Call `POST /v1/invoices/:id/compute` with `?sync=true` for blocking execution, or default async mode that returns a workflow ID for polling * **Temporal-backed processing**: Async compute runs as a durable Temporal workflow with automatic retries, 10-minute timeout, and exponential backoff * **Deferred credits and taxes**: Subscription invoice computation now applies only coupons during draft — credit deductions and tax calculations are deferred to finalization, preventing premature wallet debits

## Scheduled subscription cancellation Cancel subscriptions on a specific future date instead of only immediately or at period end. * **Scheduled date cancellation**: New `scheduled_date` cancellation type accepts a `cancel_at` timestamp for precise future cancellation scheduling * **Guard validations**: The scheduled date must be in the future — the API rejects past dates with a clear validation error * **Dashboard support**: The subscription cancellation dialog in the dashboard now includes the scheduled cancellation option with a date picker

## Integration events system Integrations now react to system events in real time — invoice finalization and customer creation automatically trigger vendor sync workflows. * **Event-driven dispatch**: A dedicated Kafka consumer group listens to `system_events` and routes `invoice.finalized` and `customer.created` events to the appropriate provider sync workflows * **Integration mapping link**: New `POST /v1/integrations/link` endpoint manually links Flexprice entities to external provider records with provider-specific side effects (e.g., Razorpay customer metadata sync) * **Configurable rate limits**: Integration event processing supports per-tenant consumer group isolation and configurable rate limits via `integration_events` settings
**Other changes** * OpenTelemetry observability expanded with a generic exporter, configurable protocol, health-check filtering, and structured OTel log fields * PostgreSQL → ClickHouse sync added for prices, subscriptions, and subscription line items for analytics workloads * Wallet balance calculation now excludes future-period draft invoices from pending balance to avoid overstating committed spend * Default invoice finalization delay increased to 10 minutes — gives integrations and async workflows time to complete before sealing * Skipped invoices are now hidden from search results and restricted to subscription invoice types only * Razorpay payment links now enforce a minimum expiration buffer to prevent premature link expiry * Commitment bucket fill logic capped with `now()` for period end to prevent future-dated empty bucket entries * Temporal workflow configuration now supports max concurrent workflow limits * SDK READMEs updated with webhook handling documentation for all supported languages * Fixed credit deduction timing — credits and taxes on subscription invoices are now deferred to finalization instead of being applied during draft computation * Fixed silent zero-usage bug — `GetDistinctEventNames` failures now return errors instead of silently zeroing meter usage * Fixed calendar billing period end calculation for quarterly and half-yearly subscriptions * Fixed invoice compute API to correctly handle recomputation of skipped and draft invoices * Removed `final` keyword from ClickHouse event queries to avoid read inconsistencies * Deprecated parent customer hierarchy removed from the frontend customer views * New `POST /v1/invoices/:id/compute` endpoint with sync and async modes for on-demand invoice computation * New `POST /v1/integrations/link` endpoint to manually map Flexprice entities to external provider records * New `POST /v1/dashboard/revenue-dashboard` endpoint for per-customer revenue analytics with time-series data * Subscription cancellation API now accepts `scheduled_date` cancellation type with a `cancel_at` field
## Paddle payment integration Accept payments through Paddle with full checkout, customer sync, and webhook support — no custom code required. * **Checkout overlay**: Customers complete payments via Paddle's hosted checkout, embedded directly in your billing flow with JWT-signed checkout URLs * **Bidirectional customer sync**: Customers are automatically synced between Flexprice and Paddle, with deduplication and address management * **Webhook processing**: Transaction completions, customer creation, and address updates from Paddle are processed automatically with built-in idempotency * **Enhanced error handling**: Multi-layer idempotency checks on invoice sync prevent duplicate Paddle transactions on retries
Paddle connection setup

## Multi-cadence billing A single subscription can now contain line items with different billing periods — monthly, quarterly, and annual charges all on one subscription. * **Mixed billing periods**: Assign different billing cadences (monthly, quarterly, annual) to individual line items within the same subscription * **Smart invoice inclusion**: Line items are automatically included on the correct invoices based on their cadence and invoice timing (advance or arrear) * **Flexible cancellation strategies**: Choose between immediate, minimum-period-end, or maximum-period-end cancellation to honor varying commitment lengths

## Bulk event ingestion Ingest raw events in bulk for backfills, testing, and high-throughput pipelines with a new dedicated API endpoint. * **Bulk raw event API**: New `POST /v1/events/raw/bulk` endpoint accepts up to 1,000 raw events per request for async processing via Kafka * **Ingestion filter**: Allowlist specific external customer IDs for live billing while preserving all raw events — ideal for controlled pilot rollouts * **Sequential usage fetch**: New `GetUsageBulkSync` method provides reliable, sequential meter usage lookups with per-meter timeout protection for billing accuracy

## Draft subscriptions Create subscriptions in a draft state before activating them, allowing configuration and invoice preview before going live. * **Draft lifecycle**: Create subscriptions with `DRAFT` status that skip invoice generation and payment processing until explicitly activated * **Invoice preview**: Preview estimated charges for draft subscriptions before activation * **Dashboard support**: Draft subscriptions now appear in customer subscription lists and support activation with configurable start dates
## Invoice overpaid tracking Track and display overpaid amounts when customers pay more than the invoice total. * **Overpaid amount display**: Invoices now show an `overpaid_amount` field when the payment exceeds the amount due * **Status visibility**: A new "Overpaid" payment status is displayed in invoice tables and detail views
**Other changes** * Webhook delivery now uses Kafka for reliable, distributed event processing — in-memory PubSub support has been removed * Sentry logging enhanced with log-level filtering, unique per-request trace IDs, and health endpoint noise reduction * Multi-addon entitlement fetching improved to support multiple instances of the same add-on per subscription * Onboarding experience refreshed with welcome banners, embedded video tutorials, and quick-start guide cards * MCP scope annotations added to Swagger documentation for better AI-agent interoperability * Webhook event types now documented in the OpenAPI spec * Go Docker image bumped to golang:1.24-alpine * Fixed inactive line item handling for previous billing period calculations * Invoice idempotency key generation scoped to minute-level precision to prevent duplicate invoices within the same billing cycle * Add-on line item termination now correctly handled during subscription cancellation * Webhook secrets are now obscured in API responses for improved security * Reverted overly strict settings validation that blocked certain configuration reads * New `POST /v1/events/raw/bulk` endpoint for bulk raw event ingestion * Subscriptions API now supports listing and filtering by `DRAFT` status * Event ingestion filter setting added for customer-level allowlisting * GitHub release step added for Go SDK CI pipeline
## Organization members & dashboard access Invite teammates to your organization and manage dashboard access from the Flexprice UI. * Invite and manage team members within your organization directly from the dashboard.
## Feature grouping Organize features into logical groups for cleaner plan configuration and customer-facing entitlement display. * **Logical feature groups**: Group related features together for better organization and clarity
Create feature with group

**Other changes** * General UI optimizations for improved performance and consistency * Enhanced navigation and visual polish across the dashboard
## Settings overhaul Settings have been restructured for faster navigation and a clearer separation of tenant, billing, and integration configurations. * **Revamped settings**: Improved organization and discoverability of configuration options

## Custom dashboard theming Personalize the Flexprice dashboard with your brand colors and visual preferences. * **Theme customization**: Apply custom colors and branding to match your organization’s identity
**Other changes** * Sentry spans now include user ID for more actionable error attribution and debugging
## Plan cloning Duplicate an existing plan with all its prices and configuration as a starting point for a new plan. * **Clone plans**: Quickly create new plans based on existing configurations
Clone plan from plan menu

## Multiple billing periods A single plan can now support multiple billing cadences (monthly, annual, etc.) simultaneously. * **Flexible billing cadences**: Offer customers different billing period options within the same plan


## Price grouping Added support for grouping in prices for better organization and display. * **Group prices**: Organize related prices into logical groups
Assign group to plan charges

## Wallet alerts Real-time wallet balance monitoring with configurable alert thresholds. * **Real-time balance event processing**: Wallet alert triggers now process all balance events with improved cache handling for accuracy * **Ongoing balance notifications**: Low-balance alerts are sent continuously as the wallet balance stays below threshold, not just on first breach * **Alert cache expiration**: Alert cache entries now expire after a configurable max-alive window to prevent stale suppression of alerts
Wallet alert settings

## API cleanup Simplified the onboarding surface by removing deprecated APIs. * **Deprecated tenant APIs removed**: Removed deprecated tenant creation APIs to streamline onboarding

**Other changes** * Plan-level price sync lock implemented with a configurable TTL (default 2 hours) to prevent conflicting concurrent updates
## Parent subscriptions Group related subscriptions under a parent for consolidated billing and hierarchy management. * **Subscription hierarchy**: Organize subscriptions with parent-child relationships

## Commitment duration Define minimum commitment periods on subscriptions to enforce contract terms. * **Backend support**: Define commitment periods programmatically via API * **Dashboard configuration**: Configure and view commitment durations directly from the subscription management dashboard

## Multiple instance add-ons Attach the same add-on multiple times to a single subscription with independent quantities and pricing. * **Flexible add-on instances**: Support for multiple instances of the same add-on per subscription

## Custom analytics Build and view custom usage and revenue breakdowns tailored to your business metrics. * **Custom dashboards**: Create analytics views specific to your business needs * **Overridden price tracking**: Analytics now reflect overridden prices accurately
Usage breakdown by group in analytics
**Other changes** * Temporal workflow history and filters for better observability into background job execution * VAPI pricing units added to support voice API consumption-based billing * Edit subscription UI improvements * Charges search API now supports filtering for more precise query results
## Credit grant editing Modify the amount, expiry, or metadata of existing credit grants without needing to void and recreate them. * **Edit existing grants**: Update credit grant details directly without voiding

## Prepaid and postpaid wallets Choose between prepaid balance top-ups or postpaid usage-based wallet modes per customer. * **Wallet modes**: Configure wallets as prepaid or postpaid based on customer needs

## Backdated pricing Create and update prices with past effective dates to accurately reflect historical billing arrangements. * **Backdated price creation**: Create prices with a past effective date * **Retroactive price updates**: Update existing prices retroactively to correct or adjust past pricing configurations
## Sentry and observability improvements We've reduced noise and improved traceability for background workflows. * **Repository-level span reduction**: Disabled repository-level Sentry span creation to reduce quota usage * **Temporal integration**: Integrated Sentry into Temporal workflows via a dedicated worker interceptor, improving traceability for background tasks
## Wallet & credit accuracy Enhanced wallet logic to correctly compute and expose credits available breakdown when retrieving real-time balance. * **Transaction status verifiction**: Wallet eligibility logic now verifies transaction status is completed before applying credits * **Workflow optimizations**: Overall wallet workflow optimizations for performance and correctness
## Subscription API enhancements Added a new API to fetch a subscription by ID with a minimal response and optional expand parameters.
## Pricing & revenue analytics Pricing validation and analytics capabilities have been extended. * **Maximum active prices**: Added validation for the maximum number of active prices to prevent configuration errors * **Currency-segregated revenue trends**: Revenue analytics extended to support currency-segregated revenue trends, recent subscriptions, and invoice payment status reporting
## Add-ons & usage pricing Add-on line item commitment support added to better model commitment/overage patterns. * **Usage-based prices within add-ons**: Support for usage-based prices within add-ons was introduced, enabling richer usage billing models * **Add-on line item commitment**: Support for add-on line item commitment was added
**Other changes** * Logging cleanup: enhanced content and removed unnecessary entity logs * Internal workflow changes to improve consistency and reduce noise in observability * Subscription start date precision normalized to milliseconds for consistency * The new subscription retrieval API with minimal response and expand parameters provides a leaner query option for clients
## Bulk import and export in the dashboard You can now import features and prices and run managed S3 exports from the UI. Billing and credit usage reports are available for download. * **Import features and prices**: Added UI support for importing features and prices, matching backend bulk-import capabilities.
Bulk import interface
* **S3 exports**: Managed exports and downloadable reports for billing and credit usage
## Dashboard analytics improvements Revenue trends and customer usage are clearer and more accurate in the dashboard. * **Currency-segregated revenue**: Revenue trends and invoice payment status now reflect the correct metrics in the UI * **Customer usage**: Improved visibility into usage at the customer level
Currency-segregated revenue trends
## Region and routing stability The frontend region selector now correctly handles US-region routing, including [us.flexprice.io](http://us.flexprice.io).
**Other changes** * Improved filtering, sorting, and usability across pricing, feature, and subscription lists * Enhanced subscription views with clearer overrides and metadata * Add-on configuration UI improved, supporting commitments and usage-based context * Query builder supports multi-select "Select All" * UI polish across revenue charts and subscription tables * Fixed onboarding issues for organization name and tenant persistence * Improved loading and error states in analytics views * Fixed revenue tooltip inconsistencies from partial invoice data * Improved frontend integration with updated analytics and usage APIs * Customer usage views backed by existing usage endpoints
## Wallet and credit balance correctness Wallet balances are now more accurate and transparent. Credits available reflect what customers can actually use, and wallet operations are safe under concurrency. * **Wallet balances**: Clear `credits available breakdown`, improving transparency and accuracy * **Top-ups**: Purchased credit top-ups now correctly contribute to usable credit balances * **Eligibility**: Credit eligibility now strictly applies only to completed transactions * **Advisory locking**: Wallet operations are protected with advisory locking to reduce race conditions under concurrency
## Add-on pricing flexibility Add-ons now support usage-based pricing and commit-and-overage billing. * **Usage-based pricing**: Add usage-based prices to add-ons * **Add-on line-item commitments**: Introduced to support commit-and-overage billing models
## Caching and performance groundwork Introduced a Redis cache layer with environment-specific key prefixes to support multi-environment deployments safely.
**Other changes** * Added validation to prevent exceeding the maximum number of active prices * Extended feature filters to support `not-contains` queries * Improved wallet balance performance using the updated balance API path * Logging noise reduced by removing unnecessary debug and print statements * Subscription start dates normalized to millisecond precision * Reduced unnecessary observability overhead by removing redundant spans from data layers * Added a lightweight subscription fetch-by-ID API with optional expand parameters * Removed support for fetching inactive prices via lookup keys * Analytics APIs now expose recent subscriptions and invoice payment status
## Import, export, and workflow foundations Backend support for bulk import and S3 exports is in place. Invoice processing and workflow reliability are improved. * **Bulk import**: Added backend support for bulk importing `prices` and `features` * **S3 exports**: Added managed `S3 export support` for reports such as credit usage * **Invoice and usage**: Improved invoice processing alignment with feature usage tracking * **Workflows**: Strengthened workflow reliability with improved Temporal identity handling and timeout tuning
## Subscription and credit grant controls You can cancel future subscription grants and set end dates on credit grants. * **Cancel future grants**: Added support for cancelling future subscription grants * **Credit grant end-date**: Added end-date support for credit grants
**Other changes** * Reintroduced price lookup keys in a controlled and validated manner * Improved tenant and environment isolation for exports * General workflow stability and timeout tuning * Fixed wallet balance discrepancies affecting “credits available” * Improved reliability of scheduled background workflows * Import and export endpoints for prices and features made available
For release history before these updates, see our [GitHub releases](https://github.com/flexprice/flexprice/releases). # Checkout Sessions API Source: https://docs.flexprice.io/docs/checkout/checkout-sessions Create a time-bounded payment session that activates a subscription on successful payment. A Checkout Session represents a single payment attempt. Call `POST /v1/checkout/sessions`, get back a payment URL, redirect your customer, and Flexprice activates the subscription once payment is confirmed. ## Session lifecycle | Status | Meaning | | ----------- | ------------------------------------------------------------ | | `initiated` | Session created; payment provider call in progress | | `pending` | Payment URL returned; awaiting customer payment | | `completed` | Payment confirmed; subscription activated, invoice finalized | | `failed` | Payment failed or provider cancelled the link | | `expired` | Session timed out without payment | ## Create a Checkout Session ```bash theme={null} curl -X POST https://api.flexprice.io/v1/checkout/sessions \ -H "Authorization: Bearer " \ -H "X-Environment-ID: " \ -H "Content-Type: application/json" \ -d '{ "customer_external_id": "cust_abc123", "action": "create_subscription", "payment_provider": "razorpay", "configuration": { "create_subscription_params": { "plan_id": "plan_xyz", "currency": "INR", "billing_period": "monthly" } }, "success_url": "https://app.example.com/checkout/success", "failure_url": "https://app.example.com/checkout/failure", "cancel_url": "https://app.example.com/checkout/cancel", "idempotency_key": "order_unique_ref_001" }' ``` **Response (201):** ```json theme={null} { "id": "chk_01HXXX", "customer_id": "cust_01HABC", "action": "create_subscription", "checkout_status": "pending", "payment_provider": "razorpay", "expires_at": "2024-01-01T12:15:00Z", "payment_action": { "type": "payment_link", "url": "https://rzp.io/l/XXXXXXX" } } ``` Redirect the customer to `payment_action.url`. Each session has its own link. Do not reuse URLs across sessions. ### Request fields | Field | Required | Description | | --------------------------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------- | | `customer_external_id` | Yes | Your identifier for the customer. Must already exist in Flexprice. | | `action` | Yes | Billing action to perform. `create_subscription` is supported. | | `payment_provider` | Yes | Payment gateway to use. See [Choose your provider](/docs/checkout/overview#choose-your-payment-provider). | | `configuration.create_subscription_params.plan_id` | Yes | The plan to subscribe the customer to. | | `configuration.create_subscription_params.currency` | Yes | Billing currency (e.g., `INR`, `USD`). | | `configuration.create_subscription_params.billing_period` | No | `monthly`, `quarterly`, `annual`, etc. Defaults to the plan's period. | | `configuration.create_subscription_params.start_date` | No | Subscription start date. Defaults to now. | | `success_url` | No | Where to redirect after successful payment. | | `failure_url` | No | Where to redirect after a failed payment. | | `cancel_url` | No | Where to redirect if the customer cancels. | | `idempotency_key` | No | Prevents duplicate sessions on retry. A duplicate key for an active session returns 409. | ## Get a Checkout Session ```bash theme={null} curl https://api.flexprice.io/v1/checkout/sessions/ \ -H "Authorization: Bearer " \ -H "X-Environment-ID: " ``` Use this to confirm `checkout_status` on your redirect pages before showing the customer a result. ## Delete a Checkout Session ```bash theme={null} curl -X DELETE https://api.flexprice.io/v1/checkout/sessions/ \ -H "Authorization: Bearer " \ -H "X-Environment-ID: " ``` Sessions in a terminal state (`completed`, `failed`, `expired`) cannot be deleted. ## Webhook events Subscribe to these in **Settings > Webhooks** to drive fulfillment from your backend. | Event | Fired when | | ---------------------------- | --------------------------------------------------------- | | `checkout.session.initiated` | Session created and payment URL returned | | `checkout.session.completed` | Payment confirmed; subscription active, invoice finalized | | `checkout.session.failed` | Payment failed or provider cancelled | | `checkout.session.expired` | Session timed out without payment | Safe to receive the same event twice. If your payment provider delivers `payment_link.paid` more than once, Flexprice ignores the duplicate. Your webhook handler should also be idempotent for the same reason. ## Requirements **Customer must exist first.** The `customer_external_id` must refer to a customer already in Flexprice. Create them at sign-up if they don't exist yet. **Plan must have a non-zero charge.** Checkout requires a payable invoice amount to generate a payment link. Plans with only free or zero-amount charges will cause session creation to fail. **Idempotency on retry.** If your create call times out, retry with the same `idempotency_key`. You'll get back the existing session instead of a duplicate. Configure the webhook events Razorpay needs to send Flexprice. Full end-to-end integration with code examples. **API References** * [Create a Checkout Session](/api-reference/checkout/create-checkout-session) * [Get a Checkout Session](/api-reference/checkout/get-checkout-session) * [Delete a Checkout Session](/api-reference/checkout/delete-checkout-session) # Implementation Guide Source: https://docs.flexprice.io/docs/checkout/implementation-guide Step-by-step guide to integrating Flexprice Checkout: backend setup, customer redirect, webhook handling, and go-live checklist. This guide covers the complete integration: backend session creation, frontend redirect, webhook handling, and going live. By the end, customers will be able to select a plan, pay, and get an active subscription without any manual step on your side. ## Prerequisites * [ ] Payment provider connected to Flexprice: see [Razorpay Setup](/docs/checkout/razorpay-checkout) * [ ] Required webhook events enabled on your provider dashboard * [ ] Flexprice webhook endpoint registered to receive `checkout.session.*` events * [ ] Customer record exists in Flexprice before the checkout starts * [ ] Plan has at least one non-zero charge ## Architecture ``` Your frontend Your backend Flexprice Razorpay ───────────── ──────────── ───────── ──────── User clicks POST /checkout/ Draft sub + Payment "Subscribe" ───────► sessions ──────► invoice ──► link created Returns URL ◄───────────────────────────────────── Redirect ◄─────────── User pays ──────────────────────────────────────────────────► Checkout ◄── payment_link.paid ────── Activates subscription Finalizes invoice ◄── checkout.session.completed ────────── Fulfill order ◄────── ``` ## Step 1: Create a customer A customer must exist in Flexprice before a session can be created. Create one at sign-up: ```bash theme={null} curl -X POST https://api.flexprice.io/v1/customers \ -H "Authorization: Bearer " \ -H "X-Environment-ID: " \ -H "Content-Type: application/json" \ -d '{ "external_id": "user_12345", "name": "Jane Smith", "email": "jane@example.com", "currency": "INR" }' ``` Use the `external_id` as your reference in subsequent calls. ## Step 2: Create a Checkout Session Call this from your backend only: never expose your API key to the frontend. ```bash theme={null} curl -X POST https://api.flexprice.io/v1/checkout/sessions \ -H "Authorization: Bearer " \ -H "X-Environment-ID: " \ -H "Content-Type: application/json" \ -d '{ "customer_external_id": "user_12345", "action": "create_subscription", "payment_provider": "razorpay", "configuration": { "create_subscription_params": { "plan_id": "plan_pro_monthly", "currency": "INR", "billing_period": "monthly" } }, "success_url": "https://app.example.com/welcome?session_id={CHECKOUT_SESSION_ID}", "failure_url": "https://app.example.com/pricing?error=payment_failed", "cancel_url": "https://app.example.com/pricing", "idempotency_key": "order_" }' ``` Two things worth noting here: * `{CHECKOUT_SESSION_ID}` in `success_url` is a placeholder that Razorpay replaces on redirect. Use it to identify which session the customer just completed. * `idempotency_key` tied to your internal order ID means a retry on timeout gives you back the same session, not a duplicate. Store the returned `id` (`chk_...`) in your database alongside the order. ## Step 3: Redirect the customer Return the `payment_action.url` from your backend to your frontend and redirect immediately: ```javascript theme={null} window.location.href = session.payment_action.url; ``` Do a full-page redirect, not an iframe. The payment link expires after 15 minutes, so don't add unnecessary steps before the redirect. ## Step 4: Handle the webhook Do not rely on the redirect URL to confirm payment: it can be bypassed. The `checkout.session.completed` webhook is the authoritative signal. Register your endpoint in **Settings → Webhooks**, then handle the events: ```javascript theme={null} app.post('/webhooks/flexprice', express.raw({ type: 'application/json' }), async (req, res) => { const { event_type, data } = JSON.parse(req.body); res.status(200).send('ok'); // respond fast, process async if (event_type === 'checkout.session.completed') { // subscription is active, invoice is finalized await grantAccess(data.customer_id); await sendWelcomeEmail(data.customer_id); } if (event_type === 'checkout.session.failed') { await notifyPaymentFailed(data.id); } if (event_type === 'checkout.session.expired') { await clearPendingState(data.id); } }); ``` Make this handler idempotent: Flexprice delivers events at least once. Check whether you've already fulfilled the order before acting. ## Step 5: Handle success and failure pages Use the redirect URLs for UX only, not for fulfillment logic. **Success page**: poll the session to confirm before showing a result: ```bash theme={null} curl https://api.flexprice.io/v1/checkout/sessions/ \ -H "Authorization: Bearer " \ -H "X-Environment-ID: " ``` If `checkout_status` is `completed`, show the success state. If it's still `pending`, the webhook hasn't arrived yet: show a "processing" state and poll again in a few seconds. **Failure or cancel page:** The customer can start over. Create a new session. The previous session and everything it created are cleaned up automatically. ## Step 6: Test the integration Use your provider's test credentials before switching to live keys. For Razorpay test cards and UPI IDs, see [Razorpay Checkout Setup](/docs/checkout/razorpay-checkout#step-4-test-the-flow). After a test payment, verify: * [ ] Session `checkout_status` is `completed` * [ ] Subscription is `active` * [ ] Invoice is `finalized` * [ ] Your backend received `checkout.session.completed` * [ ] Fulfillment ran (access granted, emails sent, DB updated) ## Error reference | Scenario | Response | Fix | | ---------------------------------------------- | ---------------------------------- | ----------------------------------------------------------- | | Customer doesn't exist | 404 | Create the customer before starting checkout | | Plan produces zero-amount invoice | 400 | Use a plan with at least one non-zero charge | | Duplicate `idempotency_key` for active session | 409 | Fetch the existing session; use a new key for a new attempt | | Customer doesn't pay in time | Session expires; drafts cleaned up | Let the user start a new checkout | | `payment_link.paid` not enabled | Subscription stays inactive | Enable the event in your payment provider dashboard | ## Go-live checklist * [ ] Switch provider connection from test keys to live keys * [ ] Confirm all required webhook events are enabled on the production URL (not test) * [ ] `success_url`, `failure_url`, `cancel_url` point to production domains * [ ] Webhook endpoint responds within 5 seconds and uses HTTPS * [ ] Fulfillment handler is idempotent Test credentials and webhook event configuration. Full field reference for the create session call. # Checkout Source: https://docs.flexprice.io/docs/checkout/overview Accept payment and activate subscriptions in a hosted checkout flow, without building any payment UI. Flexprice Checkout is a hosted payment integration that handles the full subscription activation sequence for you. Connect a payment provider, create a session from your backend, and redirect your customer. Flexprice takes care of the payment page, the subscription, the invoice, and the cleanup if something goes wrong. It is the fastest way to go from a pricing page to an active, paying subscriber. ## When to use Checkout Use Checkout when your customers initiate and pay for their own subscription: a self-serve pricing page, an upgrade flow, or a sign-up gate. If you create subscriptions on behalf of customers from your own backend (after a sales call, from an admin dashboard, or via contract), use the [Subscriptions API](/docs/subscriptions/customers-create-subscription) instead. ## How it works Your backend creates a Checkout Session with the customer, plan, and payment provider. Flexprice generates a hosted payment page URL and returns it to you. You redirect the customer there. Once payment is complete, Flexprice activates the subscription and finalizes the invoice. If the customer cancels, the session times out, or the payment fails, Flexprice cleans up all draft records automatically. ## Choose your payment provider Each provider has its own setup page with connection instructions, webhook configuration, and test credentials. Accept payments via Razorpay payment links. ## Start building Create, retrieve, and manage sessions. Backend, frontend redirect, webhook handling, and go-live checklist. Subscribe to `checkout.session.*` events to trigger fulfillment in your system. Create subscriptions directly without a payment step. # Razorpay Checkout Setup Source: https://docs.flexprice.io/docs/checkout/razorpay-checkout Configure Razorpay to work with Flexprice Checkout Sessions, including the exact webhook events required. Before creating your first Checkout Session with Razorpay, you need a connected Razorpay account and the correct webhook events enabled in your Razorpay dashboard. ## Step 1: Connect Razorpay Follow the [Razorpay Connection Setup guide](/integrations/razorpay/connection-setup) to add your API keys and webhook secret. Come back here once the connection is active. ## Step 2: Enable the required webhook events Go to your Razorpay Dashboard: **Settings → Webhooks → \[your Flexprice endpoint]** and enable all five events: | Event | Required for | | ------------------------ | ---------------------------------------------------- | | `payment.captured` | Payment reconciliation | | `payment.failed` | Payment failure handling | | `payment_link.paid` | Activating the subscription when payment completes | | `payment_link.cancelled` | Cleaning up when a customer cancels the payment link | | `payment_link.expired` | Cleaning up when the payment link expires | If `payment_link.paid` is not enabled, Flexprice will never receive payment confirmation and the subscription will stay inactive. Verify all five events are enabled before testing. ### Webhook URL Your Flexprice webhook URL follows this format: ``` https://api.cloud.flexprice.io/v1/webhooks/razorpay// ``` Find your `TENANT_ID` and `ENVIRONMENT_ID` in **Settings → General**. ## Step 3: Verify the webhook secret Flexprice validates the signature on every inbound Razorpay webhook. If the secret doesn't match, events are rejected silently. Go to **Settings → Connections → Razorpay** in Flexprice and confirm the webhook secret matches what's shown in your Razorpay dashboard for that endpoint. ## Step 4: Test the flow Use Razorpay test mode before going live. **Test cards:** | Network | Card number | CVV | Expiry | | ---------- | --------------------- | ------------ | --------------- | | Visa | `4386 2894 0766 0153` | Any 3 digits | Any future date | | Mastercard | `2305 3242 5784 8228` | Any 3 digits | Any future date | **Test UPI:** | UPI ID | Simulates | | ------------------ | ------------------ | | `success@razorpay` | Successful payment | | `failure@razorpay` | Failed payment | After a test payment, confirm: * Session status is `completed` * Subscription status is `active` * Invoice status is `finalized` * Your server received the `checkout.session.completed` event If the session stays `pending` after payment, check the Razorpay webhook delivery log and confirm all five events are enabled on the correct endpoint URL. ## Troubleshooting | Symptom | Likely cause | Fix | | ----------------------------------------- | --------------------------------------------- | --------------------------------------------------- | | Session stays `pending` after payment | `payment_link.paid` not enabled | Add it in Razorpay Dashboard → Webhooks | | Session fails immediately on creation | Razorpay connection inactive | Check status in Settings → Connections | | Webhook secret mismatch error | Secret out of sync | Re-copy the secret from Razorpay into Flexprice | | Subscription stays inactive after payment | Wrong tenant or environment ID in webhook URL | Verify the URL in Razorpay matches the format above | Connect your Razorpay account to Flexprice. Create your first session once Razorpay is configured. # Get Started Source: https://docs.flexprice.io/docs/collectors/get-started Install and run the Flexprice Bento Collector with Docker or Kubernetes Get the Flexprice Bento Collector running in minutes. ## Prerequisites * Flexprice API key * Event source (Kafka, webhooks, database) ## Quick Start with Docker **1. Create `config.yaml`:** ```yaml theme={null} input: kafka: addresses: [${KAFKA_BROKERS}] topics: [usage-events] consumer_group: flexprice-collector pipeline: processors: - mapping: | root.event_name = this.event_name root.external_customer_id = this.customer_id root.properties = this.properties.map_each(p -> p.value.string()) root.timestamp = this.timestamp.or(now().format_timestamp("2006-01-02T15:04:05Z07:00")) output: flexprice: api_host: https://api.flexprice.io api_key: ${FLEXPRICE_API_KEY} scheme: https batching: count: 10 period: 2s ``` **2. Run with Docker:** ```bash theme={null} docker run -d \ --name bento-collector \ -e FLEXPRICE_API_KEY=fp_live_xxxxx \ -e KAFKA_BROKERS=broker:9092 \ -v $(pwd)/config.yaml:/bento.yaml:ro \ ghcr.io/flexprice/bento-collector:latest \ -c /bento.yaml ``` **3. Check logs:** ```bash theme={null} docker logs -f bento-collector # Expected: # INFO Flexprice output connected and ready # INFO Input type kafka is now active ``` ## Kubernetes Deployment **1. Create Secret:** ```bash theme={null} kubectl create secret generic bento-collector \ --from-literal=FLEXPRICE_API_KEY=fp_live_xxxxx \ --from-literal=KAFKA_BROKERS=broker:9092 ``` **2. Deploy:** ```yaml theme={null} apiVersion: apps/v1 kind: Deployment metadata: name: bento-collector spec: replicas: 2 selector: matchLabels: app: bento-collector template: metadata: labels: app: bento-collector spec: containers: - name: bento image: ghcr.io/flexprice/bento-collector:latest args: ["-c", "/config/config.yaml"] envFrom: - secretRef: name: bento-collector resources: requests: memory: "256Mi" cpu: "250m" ``` ## Configuration ### Batching Tune based on event volume: ```yaml theme={null} output: flexprice: batching: count: 10 # Events per batch period: 2s # Max wait time max_in_flight: 10 # Concurrent requests ``` **Guidelines:** * Low volume (\< 100/min): `count: 5, period: 5s` * Medium volume: `count: 10, period: 2s` (default) * High volume (> 1000/min): `count: 50, period: 1s` ### Kafka Performance ```yaml theme={null} input: kafka: fetch_buffer_cap: 256 # Fetch more messages for batching commit_period: 1s max_processing_period: 100ms ``` ## Troubleshooting **Events not batching?** * Increase `fetch_buffer_cap` in Kafka input * Check event volume is sufficient * Rebuild binary if code changed **Connection errors?** * Verify API key is correct * Check Kafka broker addresses * Ensure `external_customer_id` exists in Flexprice **Memory issues?** * Reduce `fetch_buffer_cap` * Lower `max_in_flight` * Set resource limits ## Environment Variables | Variable | Description | Example | | -------------------- | ----------------- | -------------------------- | | `FLEXPRICE_API_KEY` | Flexprice API key | `fp_live_abc123...` | | `FLEXPRICE_API_HOST` | API hostname | `https://api.flexprice.io` | | `KAFKA_BROKERS` | Kafka brokers | `broker:9092` | ## Observability The collector exposes Prometheus metrics at `/metrics`: ```yaml theme={null} metrics: prometheus: enabled: true prefix: bento http: enabled: true address: 0.0.0.0:4195 ``` Access metrics: `http://localhost:4195/metrics` ## Next Steps * [How It Works](/docs/collectors/how-it-works) - Architecture and advanced config * [Bento Docs](https://warpstreamlabs.github.io/bento/docs/about) - Full Bento documentation Need help? Contact **[support@flexprice.io](mailto:support@flexprice.io)** # How It Works Source: https://docs.flexprice.io/docs/collectors/how-it-works Architecture, batching, and performance optimization for the Bento Collector Deep dive into the Bento Collector's internals and optimization. ## Architecture The collector pipeline has three components: ``` Input (Kafka) → Processors (Transform) → Output (Flexprice) ``` 1. **Input**: Reads data with offset management and backpressure handling 2. **Processors**: Transforms data using Bloblang mapping language 3. **Output**: Batches and sends to Flexprice API with retries ## How Batching Works Events accumulate until either condition is met: * **Count threshold**: Batch reaches configured size (e.g., 10 events) * **Time window**: Time limit reached (e.g., 2 seconds) **Example timeline:** ``` Time: 0s 0.5s 1s 1.5s 2s Event: 1─────2──────3─────4──────5 └─▶ Batch sent (5 events, 2s elapsed) ``` The collector automatically routes to the appropriate endpoint: * **1 event**: `POST /events` * **multiple events**: `POST /events/bulk` ## Data Transformation Use Bloblang to transform your data format to Flexprice requirements: ### Type Conversion All property values must be strings: ```yaml theme={null} pipeline: processors: - mapping: | root.properties = this.properties.map_each(p -> p.value.string()) ``` **Before:** `{"duration": 245, "status": 200}`\ **After:** `{"duration": "245", "status": "200"}` ### Conditional Processing ```yaml theme={null} pipeline: processors: - mapping: | # Only process successful requests root = if this.status >= 200 && this.status < 300 { this } else { deleted() } ``` ## Performance Optimization ### Kafka Input ```yaml theme={null} input: kafka: fetch_buffer_cap: 256 # Fetch more messages at once checkpoint_limit: 1024 # Buffer messages in flight commit_period: 1s ``` ### Output Concurrency ```yaml theme={null} output: flexprice: max_in_flight: 10 # Parallel API requests batching: count: 50 period: 5s ``` **Guidelines:** * Low volume: `max_in_flight: 5` * Medium volume: `max_in_flight: 10` (default) * High volume: `max_in_flight: 20-50` ## Error Handling The collector automatically retries failed requests: ```yaml theme={null} output: flexprice: retry_max_attempts: 3 retry_backoff: initial_interval: 1s max_interval: 60s ``` **Retry sequence:** 1. Fail → Wait 1s → Retry 2. Fail → Wait 2s → Retry 3. Fail → Wait 4s → Retry 4. Drop and log error ## Observability ### Metrics Key Prometheus metrics: | Metric | Description | | ---------------------- | -------------------------- | | `bento_input_received` | Messages received | | `bento_output_sent` | Messages sent successfully | | `bento_output_error` | Output errors | | `bento_batch_created` | Batches created | ### Logging Configure log level and format: ```yaml theme={null} logger: level: INFO # DEBUG, INFO, WARN, ERROR format: logfmt # or json add_timestamp: true ``` ## Additional Resources * **Bento Docs**: [warpstreamlabs.github.io/bento](https://warpstreamlabs.github.io/bento/docs/about) * **Bloblang Guide**: [Mapping language](https://warpstreamlabs.github.io/bento/docs/guides/bloblang/about) * **Flexprice Events API**: [Events reference](/docs/event-ingestion/overview) Need help? **[support@flexprice.io](mailto:support@flexprice.io)** # Flexprice Collector Source: https://docs.flexprice.io/docs/collectors/overview Stream usage events from Kafka and other sources into Flexprice with reliable delivery The Flexprice Collector makes it easy to send usage data into Flexprice. It takes care of reading, transforming, and delivering events so you don’t have to worry about scaling or building custom pipelines. Just plug in your data source, and the collector handles the rest. ## How It Works The Flexprice Collector is built on [Bento](https://warpstreamlabs.github.io/bento/docs/about), a robust stream processing tool. Bento's strong delivery guarantees and retry capabilities make it ideal for reliable event ingestion. The collector connects your data sources (Kafka, webhooks, databases) to Flexprice through a processing pipeline with three components: * **Inputs** read data from various sources (Kafka, webhooks, databases, files) * **Processors** validate, transform, and filter data to match Flexprice format * **Outputs** send data to Flexprice API with bulk batching and retry logic ## Key Features The Flexprice Collector is optimized for high-performance usage metering: * **Automatic Batching**: Groups events for bulk ingestion (10 events or 2s by default) * **Smart Routing**: Uses `/events` for single events, `/events/bulk` for batches * **Retry Logic**: Exponential backoff ensures no data loss on network failures * **High Throughput**: Handles millions of events per day with minimal latency ## Ingesting Data into Flexprice The collector always outputs to the Flexprice API: ```yaml theme={null} output: flexprice: api_host: https://api.flexprice.io api_key: ${FLEXPRICE_API_KEY} scheme: https batching: count: 10 period: 2s ``` Transform your data to match Flexprice event format: ```yaml theme={null} pipeline: processors: - mapping: | root.event_name = this.event_name root.external_customer_id = this.customer_id root.properties = this.properties.map_each(p -> p.value.string()) root.timestamp = this.timestamp.or(now().format_timestamp("2006-01-02T15:04:05Z07:00")) ``` Configure your input source. Example with Kafka: ```yaml theme={null} input: kafka: addresses: [${KAFKA_BROKERS}] topics: [usage-events] consumer_group: flexprice-collector ``` The collector supports 100+ input sources. Learn more in the [Bento documentation](https://warpstreamlabs.github.io/bento/docs/components/inputs/about). ## Installation The Flexprice Collector is available via: * **Binaries** from GitHub Releases * **Container images** * **Kubernetes** deployment (see Get Started guide) ## Performance & Reliability The collector provides: * **Bulk Ingestion**: Batches up to 100 events per API call * **Automatic Retries**: Failed events retry with exponential backoff * **At-Least-Once Delivery**: Kafka consumer groups ensure no data loss * **Observability**: Prometheus metrics and structured logging Contact [support@flexprice.io](mailto:support@flexprice.io) for performance tuning assistance. # Model Context Protocol (MCP) Source: https://docs.flexprice.io/docs/connect/mcp-server Connect Cursor, Claude, VS Code, and other MCP clients to your Flexprice API > Connect Cursor, Claude, VS Code, and other MCP-compatible tools to your Flexprice API. The Flexprice Model Context Protocol (MCP) server provides a set of tools that AI agents can use to interact with the Flexprice API: customers, plans, subscriptions, invoices, payments, events, and more. ## What is the Flexprice MCP server? * An MCP server that exposes your Flexprice API as **MCP tools** so AI assistants can call your API without reading REST docs. * Authenticated via **API key** (no OAuth). You provide the key in your MCP client config. * Works with any MCP client that supports stdio: Cursor, VS Code, Claude Desktop, Claude Code, Gemini, Windsurf, and others. **How you can connect:** Flexprice currently supports two ways to run the MCP server. Pick what fits your workflow: * **npx (recommended):** One command, no install. Run `npx -y @flexprice/mcp-server start` with your API key and server URL. Ideal for Cursor, VS Code, Claude Code, Gemini, Windsurf, and most MCP clients. * **Local or Docker:** Clone the repo and run with Node, or use the official Docker image. Great for development, custom builds, or environments where you prefer not to use npx. ## Quick start One-click install One-click install See instructions See instructions See instructions See all options ## Authentication The MCP server uses your **Flexprice API key** only (no OAuth). * **Where to get the key:** From your Flexprice account (Dashboard or API keys settings). * **Where to set it:** In the MCP config as `--api-key-auth YOUR_API_KEY` (npx) or in the `env` object as `API_KEY_APIKEYAUTH` (local/Docker). Do not commit API keys; rotate if exposed. **Base URL / API URL (important):** The MCP server’s `--server-url` (or equivalent) must use one of FlexPrice’s API base URLs. The two allowed URLs are: * **US Region:** `https://us.api.flexprice.io/v1` * **India Region:** `https://api.cloud.flexprice.io/v1` ## Security and credentials * Do not commit API keys. Keep your key only in local MCP config or environment variables. * Rotate keys if a key is ever committed or exposed. * We recommend enabling human confirmation for write or destructive operations in your MCP client. We advise caution when giving LLMs write access to production data. Always review actions before execution. Use caution when using the Flexprice MCP with other servers to avoid prompt injection attacks. ## Installation instructions ### Cursor Click to install the MCP server configuration for Cursor. Manual installation: 1. Open **Cursor → Settings → Cursor Settings** and go to the **MCP** tab. 2. Click **+ Add new global MCP server** (or open the MCP configuration file). 3. Add the following to your `~/.cursor/mcp.json` file. Replace `YOUR_API_KEY` with your Flexprice API key. See [Cursor MCP documentation](https://docs.cursor.com/context/model-context-protocol). ```json theme={null} { "mcpServers": { "flexprice": { "command": "npx", "args": [ "-y", "@flexprice/mcp-server", "start", "--server-url", "https://us.api.flexprice.io/v1", "--api-key-auth", "YOUR_API_KEY" ] } } } ``` 4. Save and restart Cursor. ### VS Code Click to install the MCP server configuration for VS Code. Manual installation: 1. Open the Command Palette (**Ctrl+Shift+P** / **Cmd+Shift+P**) and run **MCP: Add Server** or **MCP: Open User Configuration**. 2. Add the following to your MCP config. Replace `YOUR_API_KEY` with your Flexprice API key. See [VS Code MCP documentation](https://code.visualstudio.com/docs/copilot/chat/mcp-servers). ```json theme={null} { "servers": { "flexprice": { "type": "stdio", "command": "npx", "args": [ "-y", "@flexprice/mcp-server", "start", "--server-url", "https://us.api.flexprice.io/v1", "--api-key-auth", "YOUR_API_KEY" ] } } } ``` 3. Save and restart VS Code. ### Claude Code See instructions below. Manual installation: 1. Run the following command to add the Flexprice MCP server. Replace `YOUR_API_KEY` with your Flexprice API key. ```bash theme={null} claude mcp add Flexprice -- npx -y @flexprice/mcp-server start --server-url https://us.api.flexprice.io/v1 --api-key-auth YOUR_API_KEY ``` 2. Enter Claude Code: `claude` 3. List MCP servers with `/mcp` to confirm the server is connected. See [Claude Code MCP documentation](https://docs.anthropic.com/en/docs/claude-code/mcp#configure-mcp-servers). ### Gemini See instructions below. Manual installation: 1. Run the following command. Replace `YOUR_API_KEY` with your Flexprice API key. ```bash theme={null} gemini mcp add Flexprice -- npx -y @flexprice/mcp-server start --server-url https://us.api.flexprice.io/v1 --api-key-auth YOUR_API_KEY ``` 2. Enter Gemini: `gemini` and use `/mcp list` to verify. See [Gemini CLI](https://geminicli.com/) for details. ### Windsurf See instructions below. Manual installation: 1. Open Windsurf Settings → Cascade → **Manage MCPs** → **View raw config**. 2. Add the Flexprice server to the config (same JSON structure as Cursor). Replace `YOUR_API_KEY` with your Flexprice API key. See [Windsurf MCP documentation](https://docs.windsurf.com/windsurf/cascade/mcp). ### Other clients See instructions below. For **Claude Desktop** and other MCP clients, use the same npm package config. Example (Claude Desktop: add to your Claude Desktop config file, then quit and reopen): ```json theme={null} { "mcpServers": { "flexprice": { "command": "npx", "args": [ "-y", "@flexprice/mcp-server", "start", "--server-url", "https://us.api.flexprice.io/v1", "--api-key-auth", "YOUR_API_KEY" ] } } } ``` **Config file locations:** Claude Desktop (macOS): `~/Library/Application Support/Claude/claude_desktop_config.json`; Windows: `%APPDATA%\Claude\claude_desktop_config.json`. **Alternative: run from local repo (Node).** If you cloned the Flexprice repo and run the server with `node`: ```json theme={null} { "mcpServers": { "flexprice": { "command": "node", "args": ["/path/to/mcp-server/bin/mcp-server.js", "start"], "env": { "API_KEY_APIKEYAUTH": "your_api_key_here", "BASE_URL": "https://us.api.flexprice.io/v1" } } } } ``` **Alternative: Docker (stdio).** ```json theme={null} { "mcpServers": { "flexprice": { "command": "docker", "args": [ "run", "-i", "--rm", "-e", "API_KEY_APIKEYAUTH", "-e", "BASE_URL", "flexprice-mcp" ], "env": { "API_KEY_APIKEYAUTH": "your_api_key_here", "BASE_URL": "https://us.api.flexprice.io/v1" } } } } ``` Replace `/path/to/mcp-server` and env values as needed. Save the config and restart your client. ## Example workflows Once connected, you can ask your AI assistant to: * "List my customers" or "Get customer by id …" * "Create a subscription for customer … on plan …" * "Get plan by id …" or "List plans" * "Get usage for subscription …" ## Available tools The server exposes the Flexprice API as MCP tools (one tool per OpenAPI operation). We recommend enabling human confirmation of tools and exercising caution when using the Flexprice MCP with other servers to avoid prompt injection attacks. | Resource | Example tools | | ------------- | ---------------------------------------------------------------------------- | | Customers | `get_customers`, `post_customers`, `get_customers_id` | | Plans | `get_plans`, `get_plans_id`, `post_plans` | | Subscriptions | `post_subscriptions`, `get_subscriptions_id`, `post_subscriptions_id_cancel` | | Invoices | `get_invoices`, `get_invoices_id`, `post_invoices` | | Payments | `get_payments`, `post_payments` | | Events | `post_events`, `post_events_bulk` | | Features | `get_features`, `post_features` | | Prices | `get_prices`, `post_prices` | | Wallets | `get_wallets_id`, `post_wallets_id_top_up` | For the full list, see your MCP client's tool list after connecting or the [API Reference](/api-reference/introduction). ## Troubleshooting | Symptom | Fix | | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Server does not appear | Save the MCP config and restart your client (Cursor, VS Code, Claude Desktop). | | Invalid URL or 404 | The server uses `BASE_URL` + path. Set base URL to `https://us.api.flexprice.io/v1` (include `/v1`, no trailing slash). For npx: use `--server-url https://us.api.flexprice.io/v1`. For local/Docker: set `BASE_URL` in `.env` or the MCP config `env` object. | | API or connection errors | Verify your API key and base URL. Test with `curl -H "x-api-key: YOUR_KEY" https://us.api.flexprice.io/v1/customers`. | | Rate limiting | Reduce request frequency or contact Flexprice support. | | Port in use (local run) | Change the port in config or stop the process using it (e.g. `lsof -i :3000` then `kill -9 PID`). | | Docker issues | Ensure env vars are passed into the container. Rebuild with `--no-cache` if the build fails. | ## See also * [API Reference](/api-reference/introduction): full list of Flexprice API operations (each maps to an MCP tool). # Project Architecture Source: https://docs.flexprice.io/docs/contributing-guide/architechture Understanding the project structure and architecture ## Overview Flexprice is a comprehensive **usage-based billing and subscription management platform** built in Go. The system handles the complete billing lifecycle from event ingestion through invoice generation and payment processing, designed for B2B SaaS companies requiring flexible pricing models. ## High-Level Architecture Flexprice implements a **three-tier layered architecture** with clear separation of concerns, bootstrapped using **Uber FX dependency injection framework**: ### Architecture Layers ```mermaid theme={null} graph TB subgraph "API Layer" Router["Gin HTTP Router"] Handlers["API Handlers (v1)"] Middleware["Auth & Access Control"] end subgraph "Service Layer" EventService["Event Processing"] BillingService["Billing Logic"] SubscriptionService["Subscription Management"] PaymentService["Payment Processing"] OtherServices["20+ Domain Services"] end subgraph "Repository Layer" PostgresRepos["PostgreSQL Repositories"] ClickHouseRepos["ClickHouse Repositories"] CacheLayer["In-Memory Cache"] end subgraph "Infrastructure" Postgres["PostgreSQL (Transactional)"] ClickHouse["ClickHouse (Analytics)"] Kafka["Kafka (Event Streaming)"] Temporal["Temporal (Workflows)"] S3["S3 (Document Storage)"] end Router --> Middleware Middleware --> Handlers Handlers --> EventService Handlers --> BillingService Handlers --> SubscriptionService Handlers --> PaymentService EventService --> PostgresRepos EventService --> ClickHouseRepos BillingService --> PostgresRepos BillingService --> ClickHouseRepos PostgresRepos --> Postgres ClickHouseRepos --> ClickHouse EventService --> Kafka BillingService --> Temporal ``` ## Technology Stack ### Core Technologies Flexprice uses a modern Go stack with specialized components: **Key Dependencies:** * **Gin** - HTTP routing and middleware * **Ent** - Type-safe ORM for PostgreSQL * **Uber FX** - Dependency injection framework * **ClickHouse** - Columnar database for analytics * **Kafka (Sarama)** - Event streaming platform * **Temporal** - Workflow orchestration engine * **Stripe** - Payment processing integration ## Dual-Database Strategy Flexprice employs a **specialized database strategy** for different workloads: ### PostgreSQL (Transactional Data) * **Purpose**: ACID transactions, relational data * **Data**: Subscriptions, invoices, customers, plans, prices, wallets * **Access Pattern**: Read-write, complex joins, referential integrity * **ORM**: Ent for type-safe queries ### ClickHouse (Analytical Data) * **Purpose**: High-volume inserts, aggregation queries * **Data**: Events (raw), events\_lazy, processed\_events, system\_events * **Access Pattern**: Append-only writes, time-series queries * **Tables**: Optimized for usage metering and analytics ## Event Processing Pipeline The core of Flexprice's usage-based billing is an **asynchronous event processing pipeline** powered by Kafka: ### Pipeline Stages ```mermaid theme={null} graph LR A[Application] --> B[events] A --> C[events_lazy] B --> D[Real-time Processing] C --> E[Deferred Processing] D --> F[events_post_processing] F --> G[Post Actions] D --> H[system_events] H --> I[Webhooks] ``` ## Deployment Modes Flexprice supports **multiple deployment configurations** to separate concerns and scale independently: ### Available Modes | Mode | Components | Purpose | | --------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------- | | **`local`** | API Server + Router (with event consumption handlers) + Temporal Worker | Full development environment with all services enabled | | **`api`** | API Server + Router (event consumption disabled) | Handle HTTP requests with routing but no event processing | | **`consumer`** | Router only (with event consumption handlers) | Dedicated event processing without HTTP API | | **`temporal_worker`** | Temporal workers only | Background workflows and scheduled tasks execution | ### Mode Implementation Details * **`local`**: Starts API server, registers all router handlers with event consumption enabled (`true`), starts router, and starts Temporal worker * **`api`**: Starts API server, registers router handlers with event consumption disabled (`false`), starts router only * **`consumer`**: Requires Kafka consumer, registers all router handlers with event consumption enabled (`true`), starts router without API server * **`temporal_worker`**: Starts only Temporal worker service, no API server or router ## Service Layer Architecture The service layer implements business logic through 30+ domain services organized by domain: ### Core Services * **Billing Service**: Handles invoice generation, usage calculations, and billing cycles * **Event Service**: Processes incoming usage events and manages event lifecycle * **Subscription Service**: Manages customer subscriptions, upgrades, and downgrades * **Customer Service**: Customer management and tenant operations * **Payment Service**: Payment processing and gateway integrations * **Plan Service**: Pricing plan management and feature configuration * **Wallet Service**: Prepaid credits and wallet balance management ### Service Factory Pattern All services are instantiated through factory functions that receive a centralized `ServiceParams` struct containing shared dependencies: ```go theme={null} type ServiceParams struct { // Individual Repository Interfaces AuthRepo auth.Repository UserRepo user.Repository EventRepo events.Repository ProcessedEventRepo events.ProcessedEventRepository // ... 40+ more domain specific repositories // Publishers EventPublisher publisher.EventPublisher WebhookPublisher webhookPublisher.WebhookPublisher // Services Logger *logger.Logger Config *config.Configuration DB postgres.IClient PDFGenerator pdf.Generator S3 s3.Service // http client Client httpclient.Client // Proration ProrationCalculator proration.Calculator // Integration Factory IntegrationFactory *integration.Factory } ``` ## API Layer & Routing The API layer uses **Gin framework** with a comprehensive middleware stack for security, logging, and request processing: ### Middleware Stack 1. **Recovery Middleware**: Panic recovery and graceful error handling 2. **CORS Middleware**: Cross-origin resource sharing configuration 3. **Rate Limiting**: Request throttling per API key/tenant 4. **Authentication**: JWT and API key validation 5. **Tenant Isolation**: Multi-tenant context enforcement 6. **Request Logging**: Structured logging for all requests 7. **Metrics Collection**: Prometheus metrics for observability ### Authentication & Authorization Flexprice supports multiple authentication methods: * **API Keys**: Tenant-scoped keys for programmatic access * **JWT Tokens**: User session tokens with role-based permissions * **Webhook Signatures**: HMAC verification for incoming webhooks ### API Endpoints Structure ``` /v1 ├── /auth # Authentication endpoints ├── /customers # Customer management ├── /plans # Pricing plans and features ├── /subscriptions # Subscription lifecycle ├── /events # Event ingestion ├── /invoices # Invoice generation and retrieval ├── /usage # Usage queries and aggregations ├── /payments # Payment processing └── /webhooks # Webhook management ``` ## Multi-Tenancy Architecture Flexprice implements **row-level multi-tenancy** with environment isolation enforced at multiple architectural layers: ### Tenant Isolation Strategy * **Database Level**: All tables include `tenant_id` and `environment` columns * **Middleware Level**: Request context includes tenant/environment validation * **Service Level**: All business logic operations are tenant-scoped * **Repository Level**: Automatic filtering on all database queries ### Middleware Enforcement Tenant and environment isolation is enforced through the `AuthenticateMiddleware`, which handles both authentication and tenant context extraction: ```go theme={null} func AuthenticateMiddleware(cfg *config.Configuration, secretService service.SecretService, logger *logger.Logger) gin.HandlerFunc { return func(c *gin.Context) { // Check for API key in X-API-Key header // Check for JWT token in Authorization header c.Next() } } ``` ### Authentication Methods API keys are configured with tenant and user association: * **Tenant API Keys**: Scoped to specific tenant and environment * **User Tokens**: Associated with user permissions within tenant * **System Keys**: Cross-tenant access for administrative operations ## Repository Layer The repository layer provides data access abstraction with automatic tenant/environment filtering: ### Repository Interface ```go theme={null} type Repository interface { // All methods automatically filter by tenant/environment from context GetCustomer(ctx context.Context, customerID string) (*Customer, error) CreateInvoice(ctx context.Context, invoice *Invoice) error GetUsageEvents(ctx context.Context, filters EventFilters) ([]Event, error) } ``` ### Automatic Tenant Filtering All repositories automatically append tenant and environment filters to queries using typed context constants: ```go theme={null} func (r *PostgresRepository) GetCustomer(ctx context.Context, customerID string) (*Customer, error) { tenantID := ctx.Value(types.CtxTenantID).(string) environment := ctx.Value(types.CtxEnvironmentID).(string) return r.db.Customer. Query(). Where(customer.ID(customerID)). Where(customer.TenantID(tenantID)). Where(customer.Environment(environment)). Only(ctx) } ``` All repositories are created through factory functions that receive shared dependencies like database connections, caching layers, and configuration. ## Message Processing with PubSub Router Flexprice uses **Watermill** for message routing with built-in retry mechanisms and rate limiting: ### Kafka Integration ```go theme={null} router := message.NewRouter(message.RouterConfig{}, logger) // Event processing pipeline router.AddHandler( "events_processor", "events", subscriber, "events_post_processing", publisher, eventProcessingHandler, ) ``` ### Message Processing Features * **Dead Letter Queues**: Failed messages are routed to error topics * **Retry Logic**: Exponential backoff for transient failures * **Rate Limiting**: Configurable processing rates per topic * **Metrics**: Built-in monitoring for message processing ## Temporal Workflow Orchestration Background jobs and scheduled tasks use **Temporal** for reliable execution and workflow management: ### Workflow Types * **Billing Workflows**: Monthly/annual invoice generation * **Usage Aggregation**: Periodic usage calculations * **Payment Processing**: Async payment collection workflows * **Data Exports**: Large dataset exports to S3 * **Webhook Delivery**: Reliable event notifications ### Workflow Registration ```go theme={null} func RegisterWorkflows(worker worker.Worker) { // Billing workflows worker.RegisterWorkflow(billing.GenerateInvoiceWorkflow) worker.RegisterWorkflow(billing.ProcessPaymentWorkflow) // Usage workflows worker.RegisterWorkflow(usage.AggregateUsageWorkflow) // Export workflows worker.RegisterWorkflow(export.ExportDataWorkflow) } ``` ### Worker Lifecycle Workers are deployed separately and can be scaled independently: ```go theme={null} func StartTemporalWorker(config *config.Config) { client := temporal.NewClient() worker := worker.New(client, "billing-queue", worker.Options{}) RegisterWorkflows(worker) RegisterActivities(worker) worker.Run(worker.InterruptCh()) } ``` ## External Integrations Flexprice uses a factory pattern for external service integrations, supporting multiple providers through specific integration implementations: ```go theme={null} // Integration Factory creates specific integration instances type Factory struct { config *config.Configuration logger *logger.Logger } // Specific integration getters with proper error handling func (f *Factory) GetStripeIntegration(ctx context.Context) (*StripeIntegration, error) { // Initialize and return Stripe integration with configuration return NewStripeIntegration(f.config, f.logger), nil } func (f *Factory) GetHubSpotIntegration(ctx context.Context) (*HubSpotIntegration, error) { // Initialize and return HubSpot integration with configuration return NewHubSpotIntegration(f.config, f.logger), nil } func (f *Factory) GetRazorpayIntegration(ctx context.Context) (*RazorpayIntegration, error) { // Initialize and return Razorpay integration with configuration return NewRazorpayIntegration(f.config, f.logger), nil } ``` ### Webhook Delivery Flexprice has a flexible webhook delivery system with **optional Svix integration**. By default, it uses an internal webhook system with in-memory or Kafka pub/sub: ```go theme={null} type WebhookService struct { config *config.Configuration publisher publisher.WebhookPublisher handler handler.Handler factory payload.PayloadBuilderFactory client httpclient.Client logger *logger.Logger } func (w *WebhookService) DeliverEvent(event *Event, endpoints []WebhookEndpoint) error { // Route based on configuration if w.config.Webhook.SvixConfig.Enabled { w.logger.Info("Using Svix for webhook delivery") return w.deliverViaSvix(event) } w.logger.Info("Using internal webhook system") return w.deliverViaInternalSystem(event, endpoints) } // Configuration controls webhook delivery method // svix_config: // enabled: false # Default - uses internal system // enabled: true # Optional - uses Svix for delivery ``` ## Infrastructure Services ### Monitoring & Observability **Sentry** for error tracking and performance monitoring: ```go theme={null} sentry.Init(sentry.ClientOptions{ Dsn: config.SentryDSN, Environment: config.Environment, TracesSampleRate: 0.1, }) // Error tracking in handlers if err != nil { sentry.CaptureException(err) return gin.Error{Err: err, Type: gin.ErrorTypePublic} } ``` **Pyroscope** for continuous profiling and performance analysis: ```go theme={null} pyroscope.Start(pyroscope.Config{ ApplicationName: "flexprice-api", ServerAddress: config.PyroscopeURL, ProfileTypes: []pyroscope.ProfileType{ pyroscope.ProfileCPU, pyroscope.ProfileAllocObjects, pyroscope.ProfileAllocSpace, }, }) ``` ### Document Storage Invoice PDFs are stored in **S3** with presigned URL generation for secure access: ```go theme={null} func (s *S3Service) StoreInvoice(invoiceID string, pdfData []byte) (string, error) { key := fmt.Sprintf("invoices/%s/%s.pdf", s.tenantID, invoiceID) _, err := s.client.PutObject(&s3.PutObjectInput{ Bucket: aws.String(s.bucket), Key: aws.String(key), Body: bytes.NewReader(pdfData), }) // Return presigned URL for download return s.GeneratePresignedURL(key, 24*time.Hour) } ``` ### Caching Layer Flexprice uses **in-memory caching only** for performance optimization. Caching can be enabled/disabled via configuration: ```go theme={null} // Cache interface in internal/cache/ type Cache interface { // Get retrieves a value from the cache // Returns the value and a boolean indicating whether the key was found Get(ctx context.Context, key string) (interface{}, bool) // Set adds a value to the cache with the specified expiration // If expiration is 0, the item never expires (but may be evicted) Set(ctx context.Context, key string, value interface{}, expiration time.Duration) // Delete removes a key from the cache Delete(ctx context.Context, key string) // DeleteByPrefix removes all keys with the given prefix DeleteByPrefix(ctx context.Context, prefix string) // Flush removes all items from the cache Flush(ctx context.Context) } // Usage example func (s *PlanService) GetPlan(ctx context.Context, planID string) (*Plan, error) { // Check if caching is enabled if !s.config.Cache.Enabled { return s.repo.GetPlan(ctx, planID) } // Try cache first if cached, found := s.cache.Get(ctx, planID); found { return cached.(*Plan), nil } // Cache miss - fetch from database plan, err := s.repo.GetPlan(ctx, planID) if err == nil { s.cache.Set(ctx, planID, plan, time.Hour) } return plan, err } // Configuration // cache: // enabled: true/false # Simple boolean to enable/disable caching ``` ## Configuration Management Configuration is loaded from YAML files with environment variable overrides for deployment flexibility: ```yaml theme={null} # config.yaml server: port: 8080 host: "0.0.0.0" database: postgres: host: ${DB_HOST:localhost} port: ${DB_PORT:5432} database: ${DB_NAME:flexprice} clickhouse: host: ${CLICKHOUSE_HOST:localhost} port: ${CLICKHOUSE_PORT:9000} kafka: brokers: ${KAFKA_BROKERS:localhost:29092} topics: events: "events" events_lazy: "events_lazy" processed_events: "events_post_processing" system_events: "system_events" temporal: host: ${TEMPORAL_HOST:localhost:7233} namespace: ${TEMPORAL_NAMESPACE:default} ``` ### Configuration Structure ```go theme={null} type Configuration struct { Deployment DeploymentConfig Server ServerConfig Auth AuthConfig Kafka KafkaConfig ClickHouse ClickHouseConfig Logging LoggingConfig Postgres PostgresConfig Sentry SentryConfig Pyroscope PyroscopeConfig Event EventConfig DynamoDB DynamoDBConfig Temporal TemporalConfig Webhook Webhook Secrets SecretsConfig Billing BillingConfig S3 S3Config Cache CacheConfig EventProcessing EventProcessingConfig EventProcessingLazy EventProcessingLazyConfig EventPostProcessing EventPostProcessingConfig FeatureUsageTracking FeatureUsageTrackingConfig FeatureUsageTrackingLazy FeatureUsageTrackingLazyConfig EnvAccess EnvAccessConfig FeatureFlag FeatureFlagConfig Email EmailConfig } ``` ## Application Lifecycle The application lifecycle is managed by **Uber FX** with hooks for startup and shutdown: ```go theme={null} func main() { fx.New( // Configuration fx.Provide(config.Load), fx.Provide(logger.New), // Infrastructure fx.Provide(database.NewPostgres), fx.Provide(database.NewClickHouse), fx.Provide(kafka.NewProducer), fx.Provide(temporal.NewClient), // Services fx.Provide(service.NewBillingService), fx.Provide(service.NewEventService), // HTTP Server fx.Provide(server.New), fx.Invoke(server.RegisterRoutes), // Lifecycle hooks fx.Invoke(func(lc fx.Lifecycle, server *server.Server) { lc.Append(fx.Hook{ OnStart: server.Start, OnStop: server.Stop, }) }), ).Run() } ``` ## Notes **Key Architectural Decisions:** 1. **Dual-Database Strategy**: PostgreSQL for transactional consistency, ClickHouse for high-volume analytics 2. **Event-Driven Architecture**: Kafka enables asynchronous processing, scalability, and fault tolerance 3. **Microservices-Ready**: Deployment modes allow splitting components into separate services 4. **Strong Multi-Tenancy**: Enforced at middleware, service, and repository layers 5. **Workflow Orchestration**: Temporal provides reliable background job execution 6. **Open Integration**: Factory pattern allows multiple payment gateway implementations The architecture is designed for **high scalability**, **reliability**, and **flexibility** to support diverse pricing models while maintaining clean separation of concerns across all layers. # Backend Development Setup Source: https://docs.flexprice.io/docs/contributing-guide/backend-setup Complete guide to setting up the Flexprice backend development environment ## Prerequisites * [Golang](https://go.dev/) * [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) * Supported platforms: * Linux-based environment * macOS (Darwin) * WSL under Windows ## Quick Setup with Docker Compose The easiest way to get started is using our Docker Compose setup: ```bash theme={null} # Clone the repository git clone https://github.com/flexprice/flexprice cd flexprice # Set up the complete development environment make dev-setup ``` This command will: 1. Start all required infrastructure (PostgreSQL, Kafka, ClickHouse, Temporal) 2. Build the Flexprice application image 3. Run database migrations and initialize Kafka 4. Start all Flexprice services (API, Consumer, Worker) ## Accessing Services Once setup is complete, you can access: * Flexprice API: [http://localhost:8080](http://localhost:8080) * Temporal UI: [http://localhost:8088](http://localhost:8088) * Kafka UI: [http://localhost:8084](http://localhost:8084) (with profile 'dev') * ClickHouse UI: [http://localhost:8123](http://localhost:8123) ## Useful Commands ```bash theme={null} # Restart only the Flexprice services make restart-flexprice # Stop all services make down # Clean everything and start fresh make clean-start # Build the Flexprice image separately make build-image # Start only the Flexprice services make start-flexprice ``` ## Running Without Docker If you prefer to run the application directly: ```bash theme={null} # Start the required infrastructure docker compose up -d postgres kafka clickhouse temporal temporal-ui # Run the application locally go run cmd/server/main.go ``` ## Project Structure Flexprice follows a **clean architecture** pattern with clear separation of concerns: ``` internal/ ├── domain/ │ ├── events/ │ │ ├── model.go # Core event domain model │ │ ├── repository.go # Repository interface │ └── meter/ │ ├── model.go # Core meter domain model │ ├── repository.go # Repository interface ├── repository/ │ ├── clickhouse/ │ │ └── event.go │ ├── postgres/ │ │ └── meter.go │ └── factory.go # Factory for creating repositories ├── service/ │ ├── event.go # Event service implementation │ └── meter.go # Meter service implementation ├── api/ │ ├── v1/ │ │ ├── events.go # Event API implementation │ │ └── meter.go # Meter API implementation │ ├── dto/ │ │ ├── event.go │ │ └── meter.go │ └── router.go # API router implementation └── cmd/server/ └── main.go # Server application entry point └── docs/ └── ... # Documentation files ├── swagger │ ├── swagger.yaml # Generated Swagger API specifications ``` ### Layer Responsibilities #### Domain Layer * Contains core business logic and domain models * Defines interfaces for repositories * No dependencies on external packages or other layers #### Repository Layer * Implements data access interfaces defined in domain * Handles database operations #### Service Layer * Orchestrates business operations * Implements business logic * Uses repository interfaces for data access * Handles cross-cutting concerns #### API Layer * Handles HTTP requests/responses * Converts between DTOs and domain models * No business logic, only request validation and response formatting ### Key Design Principles 1. **Dependency Rule**: Dependencies only point inward. Domain layer has no outward dependencies. 2. **Interface Segregation**: Repository interfaces are defined in domain layer but implemented in repository layer. 3. **Dependency Injection**: Using fx for clean dependency management. 4. **Separation of Concerns**: Each layer has a specific responsibility. ### Example Flow For an event ingestion: 1. **API Layer** (`/api/v1/events.go`) receives HTTP request 2. Converts DTO to domain model 3. Calls service layer 4. **Service layer** (`/service/event_service.go`) handles business logic 5. **Repository layer** persists data 6. Response flows back through the layers ### Adding New Features 1. Define domain models and interfaces in domain layer 2. Implement repository interfaces if needed 3. Add service layer logic 4. Create API handlers and DTOs 5. Register routes and dependencies ## Testing We use `testutil` package to create test data and setup test environment. ### Testing Guidelines #### Unit Tests * Each package should have corresponding `*_test.go` files * Use table-driven tests when testing multiple scenarios * Mock external dependencies using interfaces * Aim for >80% test coverage for critical paths #### Integration Tests * Place integration tests in a separate `integration` package * Use test containers for database tests * Integration tests should use the same config structure as production #### Running Tests Use the following make commands: ```bash theme={null} make test # Run all tests make test-verbose # Run all tests with verbose output make test-coverage # Run all tests and generate coverage report ``` ## Development Credentials ### PostgreSQL * Host: localhost * Port: 5432 * Database: flexprice * Username: flexprice * Password: flexprice123 ### ClickHouse * Host: localhost * Port: 9000 * Database: flexprice * Username: flexprice * Password: flexprice123 ### Kafka * Bootstrap Server: localhost:29092 * UI: [http://localhost:8084](http://localhost:8084) (with profile 'dev') ## API Documentation The API documentation is available in OpenAPI 3.0 format at `docs/swagger/swagger-3-0.json`. ### Setting up Postman Collection 1. Open Postman 2. Click on "Import" in the top left 3. Select "Import File" 4. Choose `docs/swagger/swagger-3-0.json` 5. Click "Import" 6. Create a new environment for local development: * Name: Local * Variable: `baseUrl` * Initial Value: `http://localhost:8080/v1` * Current Value: `http://localhost:8080/v1` * Variable: `apiKey` * Initial Value: `0cc505d7b917e0b1f25ccbea029dd43f4002edfea46b7f941f281911246768fe` * Current Value: `0cc505d7b917e0b1f25ccbea029dd43f4002edfea46b7f941f281911246768fe` ## Troubleshooting ### Docker Issues 1. Ensure Docker is running properly: ```bash theme={null} docker info ``` 2. Check the status of all containers: ```bash theme={null} docker compose ps ``` 3. View logs for a specific service: ```bash theme={null} docker compose logs [service_name] ``` ### Database Connection Issues 1. Check database logs: ```bash theme={null} docker compose logs postgres docker compose logs clickhouse ``` 2. Verify the database is running: ```bash theme={null} docker compose ps postgres docker compose ps clickhouse ``` ### Kafka Issues 1. Verify Kafka is running: ```bash theme={null} docker compose logs kafka ``` 2. Check topic list: ```bash theme={null} docker compose exec kafka kafka-topics --bootstrap-server kafka:9092 --list ``` 3. View Kafka UI at [http://localhost:8084](http://localhost:8084) ## Additional Resources * [Contribution Guidelines](https://github.com/flexprice/flexprice/blob/main/CONTRIBUTING.md) * [API Documentation](https://docs.flexprice.io) * [Code of Conduct](https://github.com/flexprice/flexprice/blob/main/CODE_OF_CONDUCT.md) * [Flexprice Website](https://flexprice.io) # Code of Conduct Source: https://docs.flexprice.io/docs/contributing-guide/code-of-contuct Community guidelines and standards ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at [support@flexprice.io](mailto:support@flexprice.io) . All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the Contributor Covenant, version 1.4, available at [https://www.contributor-covenant.org/version/1/4/code-of-conduct.html](https://www.contributor-covenant.org/version/1/4/code-of-conduct.html) For answers to common questions about this code of conduct, see [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq) # Frontend Development Setup Source: https://docs.flexprice.io/docs/contributing-guide/frontend-setup Complete guide to setting up the Flexprice frontend development environment This guide will help you set up the Flexprice frontend development environment and get you ready to contribute to the dashboard and user interface components. ## Getting Started ### Prerequisites Before getting started, ensure you have: * **Node.js 16+** and npm/yarn * **Git** for version control * **VS Code** (recommended) or any modern editor * **Docker** (optional, for containerized development) ### One-Click Setup Script ```bash theme={null} # Clone the flexprice frontend repository git clone https://github.com/flexprice/flexprice-front cd flexprice-front # Run the automated setup script ./setup ``` ### Alternative: Install Latest Release ```bash theme={null} # Download and install the latest release curl -s https://api.github.com/repos/flexprice/flexprice-front/releases/latest | grep "browser_download_url.*tar.gz" | cut -d '"' -f 4 | wget -qi - tar -xzf flexprice-front-*.tar.gz cd flexprice-front-* ./setup ``` The setup script will automatically: ✅ Set up environment variables\ ✅ Install all dependencies\ ✅ Build Docker image (if Docker is available)\ ✅ Start the development server\ ✅ Open your browser to [http://localhost:3000](http://localhost:3000) ## Manual Development Setup ### Clone & Install ```bash theme={null} git clone https://github.com/flexprice/flexprice-front cd flexprice-front npm install ``` ### Environment Setup ```bash theme={null} # Copy environment template cp .env.example .env # Configure these variables in .env.local: VITE_SUPABASE_URL=your-supabase-url VITE_SUPABASE_ANON_KEY=your-supabase-anon-key VITE_API_URL=http://localhost:8080/v1 # or VITE_ENVIRONMENT=development ``` ### Start Development ```bash theme={null} npm run dev ``` Visit [http://localhost:3000](http://localhost:3000) to see your app running! ## Project Structure ``` src/ ├── components/ # UI Components │ ├── atoms/ # Basic UI elements │ │ ├── Button/ │ │ ├── Input/ │ │ └── Card/ │ ├── molecules/ # Composite components │ │ ├── Forms/ │ │ ├── Charts/ │ │ └── Tables/ │ └── organisms/ # Complex UI sections │ ├── Dashboard/ │ ├── Billing/ │ └── Analytics/ ├── pages/ # Route components ├── hooks/ # Custom React hooks ├── store/ # State management ├── utils/ # Helper functions ├── models/ # TypeScript types └── core/ # Core business logic ``` ## Self-Hosting Guide ### Docker Deployment ```bash theme={null} # Build the Docker image docker build -t flexprice-frontend . # Run the container docker run -p 80:80 \ -e VITE_API_URL=your-api-url \ -e VITE_AUTH_DOMAIN=your-auth-domain \ flexprice-frontend ``` ### Manual Deployment ```bash theme={null} # Build the application npm run build # Serve the static files # Using nginx cp nginx.conf /etc/nginx/conf.d/flexprice.conf nginx -s reload # Or using serve npx serve -s dist ``` ## Available Scripts ```bash theme={null} # Development npm run dev # Start development server npm run build # Build for production npm run preview # Preview production build # Code Quality npm run lint # Run ESLint npm run lint:fix # Fix ESLint errors npm run format # Format with Prettier ``` ## Common Development Tasks ### Adding New Features 1. **Create a feature branch:** ```bash theme={null} git checkout -b feat/new-feature ``` 2. **Create component structure:** ```bash theme={null} mkdir -p src/components/organisms/NewFeature touch src/components/organisms/NewFeature/index.tsx touch src/components/organisms/NewFeature/NewFeature.test.tsx ``` 3. **Add route (if needed):** ```typescript theme={null} // src/core/routes/Routes.tsx import NewFeature from '@/components/organisms/NewFeature' // Add to routes array { path: '/new-feature', element: } ``` ### Styling Components We use Tailwind CSS with custom configurations: ```typescript theme={null} // Example component with Tailwind const Button = ({ children }) => ( ); ``` ## Troubleshooting ### Common Issues #### Build Failures ```bash theme={null} # Clear dependencies and cache rm -rf node_modules rm -rf .vite npm install ``` #### Stale Development Server ```bash theme={null} # Reset development server rm -rf node_modules rm -rf .vite npm install npm run dev ``` # Contributing Guide Source: https://docs.flexprice.io/docs/contributing-guide/introduction Learn how to contribute to this open source project Thank you for your interest in contributing to this project! We welcome contributions from developers of all skill levels. This guide will help you get started with contributing to our codebase. ## Getting Started ### Prerequisites Before you start contributing, make sure you have: * A GitHub account * Git installed on your local machine * Basic knowledge of the technologies used in this project * Familiarity with the command line ### Flexprice Repositories * **Backend**: [https://github.com/flexprice/flexprice](https://github.com/flexprice/flexprice) - Core Flexprice backend services * **Frontend**: [https://github.com/flexprice/flexprice-front](https://github.com/flexprice/flexprice-front) - Dashboard and user interface * **Documentation**: [https://github.com/flexprice/flexprice-docs](https://github.com/flexprice/flexprice-docs) - Documentation site Each repository has its own setup instructions in the respective README files. ## Types of Contributions We welcome various types of contributions: * **Bug fixes** - Help us identify and fix issues * **Feature development** - Implement new features * **Documentation** - Improve or add documentation * **Testing** - Add or improve test coverage * **Code refactoring** - Improve code quality and performance * **UI/UX improvements** - Enhance user experience ## Before You Start 1. **Check existing issues** to see if your idea or bug report already exists 2. **Create an issue** if you're planning a significant change 3. **Discuss your approach** with maintainers before starting work 4. **Read the code of conduct** to understand our community standards ## Getting Help If you need help or have questions: * Check the existing documentation at [docs.flexprice.io](https://docs.flexprice.io) * Look through existing issues and discussions in the relevant repository * Create a new issue with the "question" label * **Join our community**: [Flexprice Community Slack](https://flexpricecommunity.slack.com) - Connect with other contributors and maintainers ## Community Links * **Slack Community**: [https://flexpricecommunity.slack.com](https://flexpricecommunity.slack.com) * **GitHub Organization**: [https://github.com/flexprice](https://github.com/flexprice) * **Website**: [https://flexprice.io](https://flexprice.io) * **Documentation**: [https://docs.flexprice.io](https://docs.flexprice.io) We’re here to support you every step of the way. # Archive a customer Source: https://docs.flexprice.io/docs/customers/archive As businesses evolve, some customers may become inactive due to churn, contract expiration, or strategic decisions. Instead of permanently deleting customer data, archiving allows you to preserve historical records while preventing further transactions or modifications. This ensures compliance with data retention policies while keeping your active customer list clean and manageable. **Steps to archive a customer** * Click on **Customers** to view the list of registered customers. * Click on the three-dot menu next to the customer you want to archive Archive Customer Option * Select Archive from the dropdown options * A confirmation dialog will appear asking if you want to archive the customer. Confirm Archive * Click **Archive** to proceed, or **Cancel** if you change your mind. Please note: * **No New Transactions**: The customer can no longer be billed or invoiced. * **Existing Subscriptions End After the Current Cycle**: Any ongoing subscriptions will continue until the end of the current billing period and will not renew. * **Data Retention**: Customer details, invoices, and payment history remain accessible for reporting purposes. # Creating a customer Source: https://docs.flexprice.io/docs/customers/create To create a new customer in Flexprice, follow these steps: Navigate to the customers dashboard by clicking on Customers from the left navigation panel. * Click on "**+ Add”**. This opens the Add Customer form Screenshot 2025-03-11 at 4.20.56 PM.png * **Enter Customer Details** * **Name**: The name of the customer. * **External Customer ID**: A unique identifier (useful for mapping to external systems). * **Email (Optional)**: Customer’s contact email for invoices and notifications. * Click on **Add Billing Details (Optional) and enter:** Screenshot 2025-03-11 at 4.21.14 PM.png * **Country**: The country for tax and compliance purposes. * **Address Line 1 & 2**: The billing address. * **State & City**: The customer’s location. * Click on **Save Customer** to complete the process. Screenshot 2025-03-11 at 4.21.55 PM.png Once created, the customer appears in the list with an **Active** status. You are not required to fill in all details initially; additional information can be added later as needed. # Customer Portal Source: https://docs.flexprice.io/docs/customers/customer-portal Generate secure sessions for customers to access their billing information The Customer Portal is a self-service interface that gives your customers secure access to their billing information, account details, and usage analytics. Instead of contacting support for every billing question, customers can independently view their subscriptions, invoices, wallet balances, and usage data. ## What is the Customer Portal? The Customer Portal empowers your customers to manage their account information independently. Through a secure, branded interface, customers can: * **View account overview** - See wallet balances, active subscriptions, and usage at a glance * **Track credits and transactions** - Monitor prepaid wallet balances and transaction history * **Access invoices** - View, search, and download invoices with payment status * **Analyze usage** - Understand feature-level usage patterns and associated costs This self-service approach reduces support burden while giving customers transparency and control over their billing information. **Secure Access** The portal uses secure session tokens that expire after 1 hour, ensuring customer data remains protected while providing convenient access. ## Portal Overview The Customer Portal is organized into four main sections, each accessible via tabs at the top of the interface: 1. **Overview** - Get a quick snapshot of your account: wallet balance, active subscriptions, and usage trends 2. **Credits** - View detailed prepaid wallet information and complete transaction history 3. **Invoices** - Browse, search, and download all invoices with payment status 4. **Usage** - Analyze feature-level usage patterns and associated costs over time Each section provides read-only access to your billing and account information, giving you full visibility into your account status without needing to contact support. ## Generating a Portal Session To give a customer access to their portal, you need to generate a secure session token. This token authenticates the customer and provides temporary access to their portal. **When to generate a session:** * When a customer requests access to their billing information * After customer onboarding to provide self-service access * When redirecting customers from your application to the portal * When a previous session token has expired **Endpoint**: `GET /v1/customers/portal/{external_id}` **Authentication**: Requires your Flexprice API key **Headers**: ``` x-api-key: ``` **Path Parameters**: | Parameter | Type | Required | Description | | ------------- | ------ | -------- | ---------------------- | | `external_id` | string | Yes | Customer's external ID | ### Example Request ```bash cURL theme={null} curl --request GET \ --url https://api.cloud.flexprice.io/v1/customers/portal/cust_123 \ --header 'x-api-key: ' ``` ```javascript JavaScript theme={null} const response = await fetch( "https://api.cloud.flexprice.io/v1/customers/portal/cust_123", { method: "GET", headers: { "x-api-key": process.env.FLEXPRICE_API_KEY, }, } ); const session = await response.json(); console.log("Token:", session.token); console.log("Expires at:", session.expires_at); ``` ```python Python theme={null} import requests response = requests.get( 'https://api.cloud.flexprice.io/v1/customers/portal/cust_123', headers={ 'x-api-key': os.environ['FLEXPRICE_API_KEY'] } ) session = response.json() print(f"Token: {session['token']}") print(f"Expires at: {session['expires_at']}") ``` ### Response ```json theme={null} { "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "url": "https://portal.yourapp.com?token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expires_at": "2025-01-06T17:27:17Z" } ``` **Response Fields**: | Field | Type | Description | | ------------ | ------ | -------------------------------------------------------- | | `token` | string | JWT token for authenticating customer portal requests | | `url` | string | Pre-built URL with the token (optional, for convenience) | | `expires_at` | string | ISO 8601 timestamp when the token expires | **Session Tokens Expire** Portal session tokens typically expire after 1 hour. You'll need to create a new session when the token expires. **Redirecting customers to the portal:** Once you receive the session response, redirect your customer to the `url` field in the response. This pre-built URL includes the token and will automatically authenticate the customer when they access it. ```javascript theme={null} // Example: Redirecting customer to portal const session = await response.json(); window.location.href = session.url; // Redirects customer to portal ``` ## Accessing the Portal from the UI Dashboard You can also access the customer portal directly from the Flexprice dashboard without making API calls. There are two ways to open a customer's portal from the UI: ### From the Customers Page 1. Navigate to the **Customers** page in your dashboard 2. Find the customer in the table 3. Click the **three-dot menu (⋮)** at the end of the customer's row 4. Select **"Open Customer Portal"** from the dropdown menu This will automatically generate a portal session and open the customer's portal in a new tab or window. Customers Page with Portal Option ### From the Customer Details Page 1. Navigate to the **Customers** page 2. Click on a customer to open their details page 3. Go to the **"Information"** tab (or any tab in the customer details view) 4. In the Customer Details section, you'll find options to: * **Copy the portal link** - Click the share icon to copy the portal URL to your clipboard * **Open the portal directly** - The portal link can be shared with the customer or opened directly Customer Details Page **Benefits of UI access:** * **Quick access** - No need to write code or make API calls * **One-click opening** - Instantly generate and open portal sessions * **Easy sharing** - Copy portal links to share with customers via email or other channels * **Automatic session management** - The UI handles token generation and expiration automatically **Sharing Portal Links** When you copy the portal link from the customer details page, you can share it directly with your customer via email, chat, or any other communication channel. The link includes an embedded session token that expires after 1 hour for security. ## Using the Portal Token Once you have the session token, you can redirect your customer to the portal URL provided in the response. The portal handles authentication automatically using the token embedded in the URL. ### Redirecting Customers The simplest approach is to use the `url` field from the session response, which includes the token as a query parameter: ```javascript theme={null} // Example: Redirecting customer to portal const response = await fetch( `https://api.cloud.flexprice.io/v1/customers/portal/${customerExternalId}`, { method: "GET", headers: { "x-api-key": process.env.FLEXPRICE_API_KEY, }, } ); const session = await response.json(); // Redirect customer to portal window.location.href = session.url; ``` ### Embedding the Portal You can also embed the portal in an iframe within your application: ```html theme={null} ``` ### Handling Token Expiration Since portal tokens expire after 1 hour, you should: 1. **Generate tokens on-demand** - Create a new session when the customer requests portal access 2. **Handle expiration gracefully** - If a customer's session expires, generate a new token and redirect them again 3. **Monitor token lifetime** - Use the `expires_at` field to track when tokens will expire ```javascript theme={null} // Example: Checking token expiration const session = await response.json(); const expiresAt = new Date(session.expires_at); const now = new Date(); if (expiresAt > now) { // Token is still valid, redirect to portal window.location.href = session.url; } else { // Token expired, generate a new one // ... generate new session } ``` ### Direct API Access If you're building a custom portal interface, you can use the session token directly in API requests. Include the token in the `X-Session-Token` header: ``` X-Session-Token: ``` The customer portal provides read-only access to: * Customer profile information * Active and past subscriptions * Invoices and payment history * Wallet balances and transactions * Usage analytics and cost data ## Overview Tab The Overview tab provides a comprehensive snapshot of your account at a glance. This is the default view when customers first access the portal. Customer Portal Overview ### Prepaid Wallet The Prepaid Wallet section displays your current credit balance in two formats: * **Credits** - The raw credit amount (e.g., "434.2 credits") * **Dollar Value** - The monetary equivalent of your credits (e.g., "\$43.42 value") This dual display helps you understand both your credit balance and its real-world value. Credits are typically used for metered billing, where usage is deducted from your prepaid balance. ### Subscriptions The Subscriptions section shows your active subscription plan: * **Plan Name** - The name of your current subscription plan (e.g., "Plan Base") * **Status** - Current subscription status, typically "Active" for active subscriptions * **Subscription Period** - The current billing period with start and end dates (e.g., "Jan 6, 2026 - Feb 6, 2026") * **Next Billing** - The date when your next billing cycle begins This information helps you track your subscription status and know when your next billing will occur. ### Usage Graph The Usage section includes an interactive graph showing your usage trends over time: * **Time Range Selector** - Choose between 1 day (1d), 7 days (7d), or 30 days (30d) views * **Usage Visualization** - A line graph showing usage patterns over the selected time period * **Y-Axis** - Represents usage quantity (e.g., 0 to 6,000 units) * **X-Axis** - Represents time (dates) The graph helps you identify usage patterns, spikes, and trends. Use the time range selector to zoom in on recent activity or get a broader view of your usage over the past month. ## Credits Tab The Credits tab provides detailed information about your prepaid wallet, including balance details and complete transaction history. Customer Portal Credits ### Prepaid Wallet Details At the top of the Credits tab, you'll see your Prepaid Wallet summary: * **Balance** - Your current credit balance displayed prominently * **Status** - Wallet status indicator (typically "Active" for active wallets) * **Dollar Value** - The monetary equivalent of your credits This gives you a quick reference for your available balance before diving into transaction details. ### Transaction History The Transaction History table provides a complete record of all credit-related transactions: | Column | Description | | ---------------- | ------------------------------------------------------------------- | | **Transactions** | Type of transaction (e.g., "Invoice Payment", "Free Credits Added") | | **Payment Date** | When the transaction occurred | | **Expiry Date** | When credits expire (if applicable) | | **Priority** | Transaction priority level | | **Amount** | Transaction amount in both dollars and credits | **Transaction Types:** * **Invoice Payment** - Credits deducted when paying invoices (shown as negative amounts, e.g., "-20 \$ / -200 credits") * **Free Credits Added** - Credits added to your account (shown as positive amounts, e.g., "+100 \$ / +1000 credits") The dual currency display (dollars and credits) helps you understand both the monetary impact and credit balance changes. Negative amounts indicate deductions, while positive amounts indicate additions to your wallet. ## Invoices Tab The Invoices tab allows you to view, search, and download all your invoices with their payment status. Customer Portal Invoices ### Invoice Table The invoice table displays all your invoices with the following information: | Column | Description | | ------------- | --------------------------------------------------------------- | | **DATE** | The date the invoice was issued | | **INVOICE #** | Unique invoice identification number (e.g., "INV-202601-00003") | | **STATUS** | Payment status of the invoice | | **AMOUNT** | Total invoice amount | | **DOWNLOAD** | Icon to download the invoice PDF | ### Invoice Status Invoices display their payment status with color-coded indicators: * **Paid** (Green) - Invoice has been successfully paid * **Pending** (Orange) - Invoice is awaiting payment This visual status helps you quickly identify which invoices need attention and which are complete. ### Search Functionality Use the search bar at the top to quickly find specific invoices. You can search by: * Invoice number * Date range * Amount * Status Simply type your search term in the "Search invoices..." field to filter the invoice list. ### Downloading Invoices Click the download icon in the **DOWNLOAD** column to save a PDF copy of any invoice. This is useful for: * Record keeping * Accounting purposes * Dispute resolution * Tax documentation ## Usage Tab The Usage tab provides detailed analytics about your feature-level usage and associated costs. When features are assigned to [groups](/docs/product-catalogue/groups), `Usage Breakdown` show data by group (e.g. GPU Usage, Add-ons). Customer Portal Usage — Usage Trend and Usage Breakdown by group ### Usage Graph The interactive usage graph visualizes your usage patterns over time: * **Time Range Selector** - Choose between 1 day (1d), 7 days (7d), or 30 days (30d) views * **Feature-Level Tracking** - Each feature is represented by a different color on the graph * **Interactive Tooltips** - Hover over data points to see detailed information: * Exact date and time * Feature name * Usage quantity at that point The graph helps you: * Identify usage spikes and patterns * Compare usage across different time periods * Understand when peak usage occurs * Track usage trends over time ### Usage Breakdown Table Below the graph, the Usage Breakdown table provides feature-level usage statistics: | Column | Description | | --------------- | ------------------------------------------------------------------ | | **Feature** | The name of the feature being tracked (e.g., "Feature B") | | **Total Usage** | Total usage quantity with unit (e.g., "5,911 calls") | | **Events** | Number of usage events recorded | | **Total Cost** | Total cost associated with this feature's usage (e.g., "\$141.86") | This breakdown helps you: * Understand which features consume the most resources * Calculate cost per feature * Identify optimization opportunities * Track usage efficiency **Interpreting the Data:** * **Total Usage** shows the cumulative usage quantity for the selected time period * **Events** indicates how many individual usage events contributed to the total * **Total Cost** represents the billing amount for this feature's usage, helping you understand the financial impact ## Understanding Your Data This section helps you interpret the information displayed throughout the portal. ### Wallet Balances Your prepaid wallet balance represents credits available for billing. When you use services, credits are deducted from this balance. The dollar value shows the monetary equivalent, helping you understand the real-world value of your credits. **Key points:** * Credits are deducted when invoices are paid or services are used * Free credits and top-ups increase your balance * Monitor your balance to ensure sufficient credits for continued service ### Subscription Status Your subscription status indicates the current state of your plan: * **Active** - Your subscription is active and billing will continue as scheduled * The subscription period shows your current billing cycle * Next billing date indicates when your next charge will occur ### Usage Graphs Usage graphs help you visualize consumption patterns: * **Spikes** indicate periods of high usage * **Trends** show whether usage is increasing, decreasing, or stable * **Time range selection** lets you zoom in on recent activity or view longer-term patterns * **Feature colors** help distinguish between different features on the same graph ### Cost Tracking Understanding costs helps you manage your budget: * **Total Cost** in the Usage Breakdown shows billing for each feature * **Invoice amounts** show what you've been charged * **Transaction history** shows credit deductions and additions * Compare usage to costs to understand your cost per unit of usage ## Portal Features The Customer Portal provides self-service access to your billing information, but it has specific capabilities and limitations. ### What You Can Do * **View account information** - See your customer profile, subscriptions, and account details * **Monitor wallet balances** - Track prepaid credits and transaction history * **Access invoices** - View, search, and download all invoices * **Analyze usage** - Review feature-level usage patterns and costs * **Track subscriptions** - Monitor active subscriptions and billing cycles ### Limitations The portal provides **read-only access**. You cannot: * Modify subscription plans * Update payment methods * Change billing information * Cancel subscriptions * Make payments These actions require contacting your account administrator or support team. ### When to Contact Support Contact support when you need to: * Change your subscription plan * Update payment methods or billing information * Cancel or pause your subscription * Resolve billing disputes * Request refunds or credits * Get help interpreting your usage data * Report technical issues with the portal ## Related Resources Learn about customer management → Understand subscription management → Learn about invoice management → Understand prepaid wallets → View full API documentation → # Overview Source: https://docs.flexprice.io/docs/customers/overview A **Customer** in Flexprice represents a business or individual that subscribes to your plans and is billed for usage. Each customer is uniquely identified and can be assigned to specific pricing plans and entitlements. **Why Manage Customers in Flexprice?** * **Track subscriptions**: Associate customers with specific plans and features. * **Automate billing**: Generate invoices based on usage. * **Manage entitlements**: Control feature access at an individual level. * **Analyze usage**: Monitor consumption for better insights. Customers can be created manually in the dashboard or through API calls for automation. To configure **Customer Hierarchy** on a subscription (for example consolidated billing across subsidiaries, or a delegated payer who pays on behalf of another customer), see [Customer Hierarchy](/docs/subscriptions/customer-hierarchy). That guide also explains how **usage, analytics, wallet balance, and invoices** behave in **consolidated** (inherited) setups versus **delegated payer**, including why child customers may show no usage in usage-summary APIs and how to use analytics with `include_children` for parent-level rollups. # Alerts and Notifications Source: https://docs.flexprice.io/docs/customers/threshold-notifications Configure proactive threshold-based alerts for wallet balances and usage — at the customer level or individual feature level — with real-time webhook delivery on every state change. Flexprice provides four types of threshold alerts so you can stay ahead of credit exhaustion, budget overruns, and usage limits. Alerts can monitor wallet balances (how much prepaid credit remains) or usage (how much has been consumed during a billing cycle) — and both can be scoped either to the overall customer or to a specific feature. All four alert types share the same three-level model (`info` → `warning` → `critical`) and deliver webhook notifications on every state transition, giving you a single consistent pattern to build on. ## Alert Types at a Glance | Alert Type | What is Monitored | Scope | Webhook Event | | ----------------------------------------------------------------- | ---------------------------------------------- | -------- | ------------------------------ | | [Low Wallet Balance](#1-low-wallet-balance-alert) | Customer prepaid wallet balance | Customer | `wallet.alert` | | [Low Feature Wallet Balance](#2-low-feature-wallet-balance-alert) | Feature-specific prepaid balance | Feature | `feature.wallet_balance.alert` | | [Total Usage](#3-total-usage-alert) | Cumulative spend in a billing cycle | Customer | `customer.usage.alert` | | [Feature-Level Usage](#4-feature-level-usage-alert) | Usage of a specific feature in a billing cycle | Feature | `feature.usage.alert` | *** ## Alert Levels and States Every alert configuration supports up to three independent threshold levels: | Level | API State | Purpose | | ------------ | ---------- | ------------------------------------------------------------- | | **Info** | `info` | Early milestone — balance or usage is approaching a limit | | **Warning** | `warning` | Action needed — balance or usage is significantly low or high | | **Critical** | `in_alarm` | Immediate action required — service continuity may be at risk | | **OK** | `ok` | Healthy — no thresholds breached | ### Conditions Each threshold is paired with a **condition** that controls which direction triggers the alert: | Condition | Triggers when | Typical use case | | --------- | ----------------- | ------------------------------------------- | | `below` | value ≤ threshold | Wallet depletion, prepaid credit exhaustion | | `above` | value ≥ threshold | Budget cap, billing cycle spend limits | ### Threshold Ordering Rules **For `below` conditions** (monitoring depletion): ``` Critical < Warning < Info ``` **For `above` conditions** (monitoring accumulation): ``` Critical > Warning > Info ``` A Warning threshold requires a Critical threshold. You cannot configure Warning without Critical. Info can be set independently. ### State Transitions Alerts transition automatically as values change. Webhooks fire on **every state change** — including recovery back to `ok`. ``` ok ──► info ──► warning ──► in_alarm ◄────────────────────────────────── (recovery) ``` State transitions can skip levels. For example, a sudden large debit may move a wallet directly from `ok` to `in_alarm`, bypassing `info` and `warning`. *** ## 1. Low Wallet Balance Alert Monitor the **prepaid wallet balance** of a customer. This alert fires when the overall credit balance crosses a configured threshold — ideal for preventing service interruptions caused by depleted prepaid credits. ### When to Use * Customer has a prepaid wallet shared across all their features * You want to notify customers (or trigger auto top-up) before credits run out * You need to monitor overall credit health across a billing cycle ### Configuration Configure thresholds when creating or updating a wallet via the API: ```json theme={null} { "alert_enabled": true, "alert_threshold_name": "info", "alert_thresholds": [ { "name": "info", "threshold": "200.00", "condition": "below" }, { "name": "warning", "threshold": "100.00", "condition": "below" }, { "name": "critical", "threshold": "0.00", "condition": "below" } ] } ``` **Example scenario:** A customer starts with \$1,000 in prepaid credits. | Balance | Alert state | Recommended action | | ------- | ----------- | --------------------------------------------- | | ≤ \$200 | `info` | Notify customer to review credit balance | | ≤ \$100 | `warning` | Prompt top-up or trigger auto top-up | | ≤ \$0 | `in_alarm` | Block new usage or notify on-call immediately | ### Webhook Payload **Event type:** `wallet.alert` ```json theme={null} { "event_type": "wallet.alert", "alert_type": "wallet_balance", "alert_status": "warning", "wallet": { "id": "wallet_01K8DC3ZAH6R55GTPFTWSR2EHF", "customer_id": "cust_01K8DBYJ0ST5GD57EB6EATEACE", "name": "Prepaid Wallet", "wallet_type": "PRE_PAID", "balance": "85.00", "credit_balance": "85.00", "currency": "usd", "alert_enabled": true, "alert_state": "warning", "alert_thresholds": [ { "name": "info", "threshold": "200.00", "condition": "below" }, { "name": "warning", "threshold": "100.00", "condition": "below" }, { "name": "critical", "threshold": "0.00", "condition": "below" } ], "wallet_status": "active", "created_at": "2025-10-25T09:45:12.273046Z", "updated_at": "2025-10-25T11:14:15.925198Z" } } ``` ### Webhook Fields | Field | Description | | ------------------------- | ------------------------------------------------------------ | | `alert_status` | Current alert state: `ok`, `info`, `warning`, or `in_alarm` | | `alert_type` | `wallet_balance` | | `wallet.balance` | Current total wallet balance | | `wallet.credit_balance` | Current credit balance | | `wallet.alert_state` | Same as `alert_status` — reflects state on the wallet object | | `wallet.alert_thresholds` | Full threshold configuration at time of event | ### Use Cases * **Prepaid SaaS** — alert at $200, $100, \$0 so customers can top up before service is affected * **API credit products** — send in-app notification at `info`, email at `warning`, pause at `in_alarm` * **Combined with auto top-up** — use `warning` as the trigger for automated wallet recharge *** ## 2. Low Feature Wallet Balance Alert Monitor the **balance of a feature-specific wallet**. Each feature can have its own prepaid allocation; this alert fires when that feature's balance crosses a threshold — independently of the customer's overall wallet. ### When to Use * Different features have separate credit allocations * You want independent spend monitoring per service (e.g. "Compute Credits" separate from "Storage Credits") * You need to notify on a per-feature basis rather than at the aggregate customer level ### Configuration Configure thresholds on the feature's alert settings: ```json theme={null} { "alert_enabled": true, "critical": { "threshold": "0.00", "condition": "below" }, "warning": { "threshold": "10.00", "condition": "below" }, "info": { "threshold": "20.00", "condition": "below" } } ``` **Example scenario:** A feature wallet for "Compute Credits" is loaded with \$500. | Feature Balance | Alert state | Recommended action | | --------------- | ----------- | -------------------------------- | | ≤ \$20 | `info` | Log and monitor — getting low | | ≤ \$10 | `warning` | Notify team, consider topping up | | ≤ \$0 | `in_alarm` | Throttle or block feature access | ### Webhook Payload **Event type:** `feature.wallet_balance.alert` ```json theme={null} { "alert_status": "in_alarm", "alert_type": "feature_wallet_balance", "event_type": "feature.wallet_balance.alert", "feature": { "id": "feat_01K8DBMBN87CY36DEFY62PEXBT", "name": "Compute Credits", "type": "metered", "alert_settings": { "alert_enabled": true, "critical": { "condition": "below", "threshold": "0.00" }, "warning": { "condition": "below", "threshold": "10.00" }, "info": { "condition": "below", "threshold": "20.00" } }, "status": "published", "meter_id": "meter_01K8DBMBN5NRFNYBMXN1G3WDWY", "created_at": "2025-10-25T09:36:40.616655Z", "updated_at": "2025-10-25T11:14:15.925198Z" }, "wallet": { "id": "wallet_01K8DC3ZAH6R55GTPFTWSR2EHF", "name": "Compute Credits Wallet", "wallet_type": "PRE_PAID", "balance": "-2.50", "credit_balance": "-2.50", "currency": "usd", "alert_enabled": true, "alert_state": "in_alarm", "wallet_status": "active", "customer_id": "cust_01K8DBYJ0ST5GD57EB6EATEACE", "created_at": "2025-10-25T09:45:12.273046Z", "updated_at": "2025-10-25T09:58:22.259833Z" } } ``` ### Webhook Fields | Field | Description | | ------------------------ | ----------------------------------------------------------- | | `alert_status` | Current alert state: `ok`, `info`, `warning`, or `in_alarm` | | `alert_type` | `feature_wallet_balance` | | `feature.id` | Unique identifier of the feature | | `feature.name` | Name of the feature being monitored | | `feature.alert_settings` | Full alert configuration including all thresholds | | `wallet.balance` | Current feature wallet balance (may be negative) | | `wallet.alert_state` | Current alert state reflected on the wallet object | ### Use Cases * **Per-feature credit allocations** — independently monitor separate feature budgets without cross-contamination * **Storage vs compute** — alert on compute credits independently from storage credits * **Tenant isolation** — set different depletion thresholds per feature based on criticality *** ## 3. Total Usage Alert Monitor **cumulative usage spend** for a customer across their active billing cycle. Unlike wallet balance alerts (which track remaining credits), usage alerts track how much has been consumed — making them ideal for enforcing soft spend caps or budget limits. ### When to Use * Customer has a monthly or annual spend budget * You want to warn customers before they exceed a contracted usage limit * You need to enforce soft or hard caps on total billing cycle spend ### Configuration Configure thresholds on the customer's usage alert settings: ```json theme={null} { "alert_enabled": true, "alert_thresholds": [ { "name": "info", "threshold": "1000.00", "condition": "above" }, { "name": "warning", "threshold": "4000.00", "condition": "above" }, { "name": "critical", "threshold": "5000.00", "condition": "above" } ] } ``` **Example scenario:** A customer has a \$5,000 monthly spend budget. | Cumulative Spend | Alert state | Recommended action | | ---------------- | ----------- | -------------------------------------------- | | ≥ \$1,000 | `info` | Log spend milestone — 20% of budget reached | | ≥ \$4,000 | `warning` | Notify account team — 80% of budget consumed | | ≥ \$5,000 | `in_alarm` | Enforce cap or escalate to customer | ### Webhook Payload **Event type:** `customer.usage.alert` ```json theme={null} { "event_type": "customer.usage.alert", "alert_type": "customer_usage", "alert_status": "warning", "customer": { "id": "cust_01K8DBYJ0ST5GD57EB6EATEACE", "external_id": "acme-corp", "name": "Acme Corp" }, "subscription": { "id": "sub_01K8DBMBN87CY36DEFY62PEXBT", "plan_id": "plan_01K8DBMBN5NRFNYBMXN1G3WDWY", "billing_period_start": "2025-11-01T00:00:00Z", "billing_period_end": "2025-11-30T23:59:59Z" }, "usage": { "current_usage_amount": "4250.00", "currency": "usd", "billing_period_start": "2025-11-01T00:00:00Z", "billing_period_end": "2025-11-30T23:59:59Z" }, "alert_settings": { "alert_enabled": true, "alert_state": "warning", "alert_thresholds": [ { "name": "info", "threshold": "1000.00", "condition": "above" }, { "name": "warning", "threshold": "4000.00", "condition": "above" }, { "name": "critical", "threshold": "5000.00", "condition": "above" } ] } } ``` ### Webhook Fields | Field | Description | | ----------------------------------- | ----------------------------------------------------------- | | `alert_status` | Current alert state: `ok`, `info`, `warning`, or `in_alarm` | | `alert_type` | `customer_usage` | | `customer.id` | Unique identifier of the customer | | `customer.external_id` | Your system's customer identifier | | `subscription.billing_period_start` | Start of the billing cycle being monitored | | `subscription.billing_period_end` | End of the billing cycle being monitored | | `usage.current_usage_amount` | Cumulative spend amount in this billing cycle | | `usage.currency` | Currency of the usage amount | | `alert_settings.alert_state` | Current alert state | ### Use Cases * **Spend caps for enterprise customers** — alert at 50%, 80%, and 100% of a contracted spend limit * **Proactive customer communication** — send automated emails at `info`, outreach at `warning`, and sales escalation at `in_alarm` * **Soft vs hard limits** — use `warning` to notify and `in_alarm` to enforce a hard stop via the API *** ## 4. Feature-Level Usage Alert Monitor **cumulative usage of a specific feature** within a billing cycle. This gives you granular visibility into per-feature consumption — independent of the customer's overall usage — so you can track individual API endpoints, compute resources, or storage usage separately. ### When to Use * Different features have separate usage budgets or rate limits * You want feature-specific alerts without noise from unrelated usage * You need to alert on physical units (e.g. API calls, GB, minutes) rather than monetary amounts ### Configuration Configure thresholds on the feature's usage alert settings: ```json theme={null} { "alert_enabled": true, "alert_thresholds": [ { "name": "info", "threshold": "250000", "condition": "above" }, { "name": "warning", "threshold": "750000", "condition": "above" }, { "name": "critical", "threshold": "1000000","condition": "above" } ] } ``` **Example scenario:** A customer has a contract for up to 1,000,000 API calls per month. | API Calls Used | Alert state | Recommended action | | -------------- | ----------- | ------------------------------------------ | | ≥ 250,000 | `info` | 25% consumed — normal tracking | | ≥ 750,000 | `warning` | 75% consumed — notify customer | | ≥ 1,000,000 | `in_alarm` | Limit reached — enforce rate cap or upsell | ### Webhook Payload **Event type:** `feature.usage.alert` ```json theme={null} { "event_type": "feature.usage.alert", "alert_type": "feature_usage", "alert_status": "warning", "feature": { "id": "feat_01K8DBMBN87CY36DEFY62PEXBT", "name": "API Calls", "type": "metered", "meter_id": "meter_01K8DBMBN5NRFNYBMXN1G3WDWY", "alert_settings": { "alert_enabled": true, "alert_state": "warning", "alert_thresholds": [ { "name": "info", "threshold": "250000", "condition": "above" }, { "name": "warning", "threshold": "750000", "condition": "above" }, { "name": "critical", "threshold": "1000000", "condition": "above" } ] }, "status": "published", "created_at": "2025-10-25T09:36:40.616655Z", "updated_at": "2025-10-25T11:14:15.925198Z" }, "customer": { "id": "cust_01K8DBYJ0ST5GD57EB6EATEACE", "external_id": "acme-corp" }, "subscription": { "id": "sub_01K8DBMBN87CY36DEFY62PEXBT", "billing_period_start": "2025-11-01T00:00:00Z", "billing_period_end": "2025-11-30T23:59:59Z" }, "usage": { "current_usage": "762450", "unit": "calls", "billing_period_start": "2025-11-01T00:00:00Z", "billing_period_end": "2025-11-30T23:59:59Z" } } ``` ### Webhook Fields | Field | Description | | ------------------------------------ | ----------------------------------------------------------- | | `alert_status` | Current alert state: `ok`, `info`, `warning`, or `in_alarm` | | `alert_type` | `feature_usage` | | `feature.id` | Unique identifier of the feature | | `feature.name` | Name of the feature being monitored | | `feature.meter_id` | The meter tracking this feature's usage | | `feature.alert_settings.alert_state` | Current alert state | | `customer.external_id` | Your system's customer identifier | | `usage.current_usage` | Cumulative feature usage in this billing cycle | | `usage.unit` | Unit of measurement (e.g. `calls`, `gb`, `minutes`) | ### Use Cases * **API rate limits** — alert at 25%, 75%, and 100% of a contracted call volume; enforce hard cap at `in_alarm` * **Compute or storage quotas** — independent thresholds per resource type without aggregate noise * **Tiered upsell** — route `warning` webhooks to your CRM to trigger upsell outreach before the customer hits their limit *** ## Webhook Delivery All four alert types are delivered as webhooks. Flexprice supports two delivery modes: | Mode | Best for | Retry behavior | | -------------------------- | ---------------------------------------- | ----------------------------------------------------------------------------------------- | | **Native** | Lightweight or internal integrations | Up to 3 retries, exponential backoff (1s → 10s, 2-minute window) | | **Svix** (Flexprice Cloud) | Production, customer-facing integrations | At-least-once delivery, dead-letter queues, HMAC-SHA256 signatures, full delivery history | Webhooks fire on **every state transition** — both when crossing a threshold and when recovering to `ok`. You will not receive repeated webhooks if the alert state does not change. See [Webhooks](/docs/webhook/webhooks) for endpoint setup, signature verification, and retry configuration. *** ## Choosing the Right Alert Type | If you want to… | Use | | -------------------------------------------------------------- | ----------------------------------------------------------------------- | | Know when a customer's overall prepaid credits are running low | [Low Wallet Balance Alert](#1-low-wallet-balance-alert) | | Know when credits for a specific feature are depleted | [Low Feature Wallet Balance Alert](#2-low-feature-wallet-balance-alert) | | Enforce a spend cap across an entire billing cycle | [Total Usage Alert](#3-total-usage-alert) | | Track usage quota for a specific feature independently | [Feature-Level Usage Alert](#4-feature-level-usage-alert) | | Both prepaid balance and spend monitoring | Combine wallet + usage alerts — they are independent and complementary | *** ## Best Practices **Set thresholds that allow time to react** — for `below` conditions, set `warning` at 2–5× the `critical` value. For `above` conditions, set `warning` at 80% and `critical` at 100% of the limit. **Handle recovery webhooks** — when a value recovers (e.g. after a top-up or billing cycle reset), Flexprice sends `alert_status: ok`. Use this to automatically re-enable services or dismiss in-app banners. **Make webhook handlers idempotent** — under retry scenarios, the same state transition may be delivered more than once. Use the combination of the entity ID (wallet or feature) + `alert_status` as an idempotency key. **Combine wallet and usage alerts** — wallet alerts track remaining prepaid credit; usage alerts track what has been spent. Using both gives you complete visibility from both directions. **Use feature-level alerts for independent budgets** — if a customer's features have separate allocations, feature-scoped alerts avoid false alarms from unrelated activity. **Test in staging first** — configure alert thresholds in a staging environment with test events before enabling in production to verify your webhook handler responds correctly at each level. *** ## Related * [Low Balance Alert (Wallet)](/docs/wallet/low-balance-alert) — detailed configuration guide for customer wallet alerts * [Feature Wallet Balance Alert](/docs/product-catalogue/features/wallet-balance-alert) — detailed configuration guide for feature-level wallet alerts * [Auto Top-Up](/docs/wallet/auto-top-up) — automatically recharge wallets when balance drops below a threshold * [Webhooks](/docs/webhook/webhooks) — endpoint setup, payload signatures, and retry behavior # Customer Timezones Source: https://docs.flexprice.io/docs/customers/timezone-aware-billing Bill customers in their local timezone. Set a timezone on a customer and Flexprice automatically aligns billing periods, usage windows, entitlement resets, and credit expirations to their local calendar. When you serve customers across geographies, billing in UTC creates a visible problem: a customer in Bangalore on a monthly plan ends their billing period at 5:30 AM local time, not midnight. Their invoice covers dates that do not match their calendar, and usage that feels like "this month" to them lands on "next month's" invoice. Customer timezones fix this by aligning every billing boundary to the customer's local time. *** ## How It Works Set a `timezone` on the customer record. Flexprice then aligns the following to the customer's local calendar: * **Billing period start and end:** each period (daily, weekly, monthly, quarterly, half-yearly, annual) begins and ends at local midnight. * **Usage windows:** usage is aggregated per local day or month, not UTC. * **Entitlement resets:** daily and monthly usage limits reset at local midnight. * **Credit grants:** prepaid credits expiring at period-end use the customer's local boundary. * **Coupon end dates:** coupons tied to billing periods expire at the local period boundary. Customers without a timezone set default to `UTC`. *** ## Setting Up ### Via the API Set the `timezone` field when creating a customer, or update it at any time: ```bash theme={null} # Create a customer with a timezone curl -X POST https://api.flexprice.io/v1/customers \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "external_id": "cust_001", "name": "Acme India", "email": "billing@acme.in", "timezone": "Asia/Kolkata" }' ``` ```bash theme={null} # Update an existing customer's timezone curl -X PATCH https://api.flexprice.io/v1/customers/{id} \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{ "timezone": "America/Los_Angeles" }' ``` Updating a customer's timezone does **not** affect active subscriptions. Only new subscriptions created after the update will use the new timezone. Use standard IANA timezone names. Common examples: | Region | IANA Name | | ----------------- | --------------------- | | India (IST) | `Asia/Kolkata` | | US Eastern | `America/New_York` | | US Pacific | `America/Los_Angeles` | | Germany | `Europe/Berlin` | | UK | `Europe/London` | | Singapore | `Asia/Singapore` | | Australia Eastern | `Australia/Sydney` | | Nepal | `Asia/Kathmandu` | Invalid timezone names are rejected with HTTP 400. ### Via the Dashboard When creating or editing a customer, select the timezone from the **Timezone** dropdown. It applies to all future subscriptions for that customer. If no timezone is selected, the customer defaults to `UTC`. *** ## What Stays the Same Changing a customer's timezone does not affect how you send events or how historical data is stored. Your event pipeline requires no changes. Existing customers without a timezone and their historical subscriptions are unaffected. *** ## Example A customer in India (IST, UTC+5:30) on a monthly calendar plan: | | Without timezone | With timezone (IST) | | --------------------- | ------------------------------ | -------------------------------- | | Daily usage resets at | 5:30 AM IST | 12:00 AM IST ✓ | | Entitlement resets at | 5:30 AM IST | 12:00 AM IST ✓ | | Invoice period | March 15 – April 1 (UTC dates) | March 15 – April 1 (IST dates) ✓ | *** ## Limitations **Changing timezone on an active subscription** Updating a customer's timezone only applies to new subscriptions. To rebill an existing subscription under a new timezone, cancel it and create a new one. **Non-standard offsets** Half-hour and 45-minute offsets such as India (UTC+5:30), Nepal (UTC+5:45), and Newfoundland (UTC-3:30) are fully supported. *** ## Related Articles Create customers via the dashboard or API. Create a subscription and set the start date for a customer. Credit grants that expire at billing period boundaries respect the customer's timezone. Coupons tied to billing periods use the customer's local timezone for expiry. *** **API References** * [Create a Customer](/api-reference/customers/create-customer) * [Update a Customer](/api-reference/customers/update-customer) * [Create a Subscription](/api-reference/subscriptions/create-subscription) # Automated Data Export to Amazon S3 Source: https://docs.flexprice.io/docs/data-exports/amazon-s3/export Automate data exports to Amazon S3 with flexible scheduling and configuration ## Overview The Amazon S3 Export feature enables automated, scheduled exports of your Flexprice data directly to Amazon S3 buckets. This robust, enterprise-grade solution allows you to configure scheduled tasks to export data at regular intervals, trigger manual exports on-demand, and track all export operations with comprehensive execution history. **Key Benefits:** * **Automated Backups**: Schedule regular exports to maintain up-to-date data backups * **Flexible Scheduling**: Export data hourly or daily based on your business needs * **Secure Storage**: Data encrypted at rest with multiple encryption options * **Compression Support**: Reduce storage costs with GZIP compression * **Execution Tracking**: Monitor all export runs with detailed execution history * **On-Demand Exports**: Trigger manual exports for immediate data exports ## Supported Data Types Flexprice currently supports exporting the following data types to S3: ### Invoices Finalized invoices without line items, taxes, and payment details. **Example Export (First Row):** ```csv theme={null} id,tenant_id,environment_id,customer_id,subscription_id,invoice_number,invoice_type,invoice_status,payment_status,currency,amount_due,amount_paid,amount_remaining,subtotal,total,total_discount,total_tax,adjustment_amount,refunded_amount,billing_sequence,description,due_date,paid_at,voided_at,finalized_at,billing_period,period_start,period_end,invoice_pdf_url,billing_reason,metadata,idempotency_key,version,status,created_by,updated_by,created_at,updated_at inv_01K76TCFSQX6JY5C43WT2G1EE8,tenant_01K0F7AQCZR5THSG9MHEZE8VKR,env_01K0F7AQEQFTV06P178C1KTR6X,cust_01K4CGWJ4493R9V2Y5PZW7CNRZ,subs_01K4M3DVXZNFRN50P9T5EKB9HV,INV-202510-00079,SUBSCRIPTION,FINALIZED,PENDING,usd,10,0,10,10,10,0,0,0,0,2,Invoice for subscription subs_01K4M3DVXZNFRN50P9T5EKB9HV,2025-10-09T05:47:27Z,,,2025-10-10T10:24:09Z,MONTHLY,2025-09-08T05:47:27Z,2025-10-08T05:47:27Z,https://pay.stripe.com/invoice/...,SUBSCRIPTION_CYCLE,"{""stripe_hosted_invoice_url"":""...""}",subscription_invoice-ff29843f8cde4859,3,published,d95842bc-7d6c-4a4e-8dc9-eb9d0780d9f3,d95842bc-7d6c-4a4e-8dc9-eb9d0780d9f3,2025-10-10T10:24:08Z,2025-10-10T10:24:12Z ``` ### Events Feature usage events and billing-related activity data. **Example Export (First Row):** ```csv theme={null} id,tenant_id,environment_id,external_customer_id,customer_id,subscription_id,sub_line_item_id,price_id,meter_id,feature_id,event_name,source,timestamp,ingested_at,period_id,qty_total,properties,unique_hash event_01K8D3HHYJVAC90K3X8R0YZA5N,tenant_01K0F7AQCZR5THSG9MHEZE8VKR,env_01K0F7AQEQFTV06P178C1KTR6X,cus_TIciSJDL8QS8OU,cust_01K8D2PERMJ6TX8ATP9C8WCP1Z,subs_01K8D3BJ78M7F0FCPFBBXJWJPG,subs_line_01K8D2Q72ESN6FPP72RA2SWWQR,price_01K42Q7VQEDETDGSA80A8WRJ2Y,meter_01K42Q6R916Q4KB9MC6S46PWQ2,feat_01K42Q6R941C3QS6Q59CGGSPFB,api_call,onboarding,2025-10-25T07:15:20Z,1970-01-01T00:00:00Z,1761376322000,20,"{""api_calls"":20}",baae5efc04d556dc97407d259169b46390946ec4330c974304e70b037aebadcb ``` ### Credit Top-Ups Completed credit wallet transactions for customers. **Example Export (First Row):** ```csv theme={null} topup_id,external_id,name,wallet_id,amount,credit_balance_before,credit_balance_after,reference_id,transaction_reason,created_at wt_01K9X5HJKM9PQR2ST3UV4WXY6Z,cust_ext_123abc,Acme Corporation,wallet_01K9X5HJKM9PQR2ST3UV4WXY6Z,500.00,1000.00,1500.00,topup_ref_abc123,Manual credit top-up for Q1 usage,2025-10-15T14:30:00Z ``` ### Credit Usage Customer-level aggregated credit balances across all wallets. This export provides a snapshot of each customer's current and real-time credit balances, aggregated from all their wallets. **Example Export (First Row):** ```csv theme={null} name,customer_external_id,customer_id,current_balance,real_time_balance,number_of_wallets Acme Corporation,cust_ext_123abc,cust_01K4CGWJ4493R9V2Y5PZW7CNRZ,146.00,146.00,1 ``` **Data Included:** * **name**: Customer name * **customer\_external\_id**: Customer external ID * **customer\_id**: Internal customer ID * **current\_balance**: Aggregated current credit balance across all wallets (decimal string) * **real\_time\_balance**: Aggregated real-time credit balance including pending charges (decimal string) * **number\_of\_wallets**: Total number of wallets for the customer **Balance Aggregation**: Balances are aggregated at the customer level across all wallets. Customers with no wallets are included with zero balances. ## Setting Up S3 Export S3 Data Exports
Page ### Prerequisites Before configuring S3 exports, ensure you have: 1. An AWS account with S3 access 2. An S3 bucket created in your desired region 3. AWS credentials (Access Key ID and Secret Access Key) 4. Appropriate IAM permissions for S3 operations ### Step 1: Create an S3 Connection An S3 connection stores your AWS credentials and serves as the authentication mechanism for all export operations. Create S3
Connection Flexprice offers two connection types: #### Option 1: Flexprice-Managed S3 Connection Use Flexprice's managed S3 infrastructure for hassle-free exports: **Benefits:** * ✅ No AWS account required * ✅ Instant setup with zero configuration * ✅ Managed security and access control * ✅ Download files directly from Flexprice dashboard **When to Use:** * Quick setup without AWS infrastructure * Don't want to manage S3 buckets and IAM policies * Need immediate data exports with minimal configuration **Flexprice-Managed Storage**: Your data will be stored securely in Flexprice-managed S3 buckets. Files are encrypted at rest and accessible via secure download links. #### Option 2: Your Own S3 Connection Connect to your own AWS S3 bucket for complete control: **Required Information:** 1. **Connection Name**: A descriptive name for your S3 connection (e.g., "Production S3 Export") 2. **AWS Access Key ID**: Your AWS access key ID * Format: Starts with `AKIA` for permanent credentials * Format: Starts with `ASIA` for temporary/session credentials * Required for authentication 3. **AWS Secret Access Key**: Your AWS secret access key * This credential is encrypted and stored securely * Never exposed in API responses 4. **AWS Session Token** (Optional): Required only for temporary credentials * Use when working with assumed roles or temporary credentials * Leave empty for permanent IAM user credentials **Security Best Practice**: Create a dedicated IAM user with minimal permissions for S3 exports. Grant only the necessary S3 permissions (`s3:PutObject`, `s3:GetObject`, `s3:ListBucket`). #### AWS IAM Policy Example Create an IAM policy with the following permissions: ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Sid": "FlexPriceS3ExportAccess", "Effect": "Allow", "Action": ["s3:PutObject", "s3:GetObject", "s3:ListBucket"], "Resource": [ "arn:aws:s3:::your-bucket-name/*", "arn:aws:s3:::your-bucket-name" ] }, { "Sid": "HeadObject", "Effect": "Allow", "Action": "s3:HeadObject", "Resource": "*" } ] } ``` Replace `your-bucket-name` with your actual S3 bucket name. Ensure the IAM user or role has this policy attached before creating the connection. ### Step 2: Configure a Scheduled Task Once your S3 connection is established, create a scheduled task to automate exports. Create Export
Task #### Configuration Options **1. Connection** * Select the S3 connection created in Step 1 * Each scheduled task can use different connections if needed **2. Entity Type** (Required) Choose what data to export: | Entity Type | Description | Data Included | | ------------------ | -------------------------- | -------------------------------------------------------------------------------- | | **invoice** | Finalized invoices | All invoice data, without line items, taxes, payments | | **events** | Feature usage events | Event data, usage metrics, customer activity | | **credit\_topups** | Credit wallet transactions | Completed credit top-ups with wallet balance details | | **credit\_usage** | Customer credit balances | Aggregated current and real-time credit balances across all wallets per customer | **3. Schedule Interval** (Required) Select how frequently exports should run: | Interval | Frequency | Cron Schedule | Best For | | ---------- | ------------------------------ | ------------- | -------------------------------------------- | | **hourly** | Every hour at :15 minutes past | `15 * * * *` | Real-time data needs, high-frequency updates | | **daily** | Every day at 00:15 AM | `15 0 * * *` | Standard backups, daily reporting | **Why the 15-minute buffer?** Scheduled exports include a 15-minute buffer after the interval boundary to ensure all data has been ingested and processed. For example, an hourly export scheduled at 10:15 AM will export data from 9:00 AM to 10:00 AM. **4. S3 Configuration** (Required) Configure S3-specific settings for your exports: ##### Bucket (Required) * The name of your S3 bucket where files will be stored * Example: `flexprice-exports-prod` * Must exist before creating the scheduled task ##### Region (Required) * AWS region where your bucket is located * Example: `us-west-2`, `eu-central-1`, `ap-south-1` * Must match your bucket's region ##### Key Prefix (Optional) * Directory path prefix for organizing files in your bucket * Example: `flexprice-exports/invoices/` * Example: `production/events/2024/` * Files will be stored as: `{key_prefix}/{filename}.csv` Use key prefixes to organize exports by environment, data type, or date. This makes it easier to manage and query exported data. ##### Endpoint URL (Optional) Custom endpoint URL for S3-compatible storage services: * **Use Case**: Connect to S3-compatible storage providers other than AWS S3 * **Examples**: * MinIO: `https://minio.example.com` * DigitalOcean Spaces: `https://nyc3.digitaloceanspaces.com` * Backblaze B2: `https://s3.us-west-002.backblazeb2.com` * **Default**: Leave empty to use AWS S3 (default) When using a custom endpoint, ensure your credentials and bucket configuration are compatible with that provider's S3 API implementation. ##### Compression (Optional) Control file compression to reduce storage costs: | Option | Description | File Extension | Compression Ratio | | -------- | ------------------------ | -------------- | ----------------------- | | **none** | No compression (default) | `.csv` | N/A (original size) | | **gzip** | GZIP compression | `.csv.gz` | \~70-80% size reduction | **When to use compression:** * Large datasets with many records * Storage cost optimization * Bandwidth-limited environments **When to skip compression:** * Small datasets (\< 1MB) * Files need to be immediately readable * Downstream systems don't support GZIP ##### Encryption (Optional) Secure your data at rest with server-side encryption: | Option | Description | Key Management | Best For | | ---------------- | --------------------------------------- | ------------------------ | ----------------------- | | **AES256** | S3-managed AES-256 encryption (default) | AWS manages keys | Standard security needs | | **aws:kms** | AWS KMS encryption | You control keys via KMS | Compliance requirements | | **aws:kms:dsse** | KMS with dual-layer encryption | Enhanced KMS security | Maximum security needs | All encryption options are server-side encryption (SSE). Data is encrypted at rest in S3. AES256 is the default and recommended for most use cases. **5. Enable/Disable Toggle** * Enable the scheduled task to start automatic exports * Disable to pause exports without deleting the configuration * Can be toggled at any time ### Step 3: Verify Configuration After creating a scheduled task: 1. **Verify Connection**: Ensure your S3 connection is validated and active 2. **Check Schedule**: Confirm the interval matches your requirements 3. **Review S3 Settings**: Double-check bucket name, region, and paths 4. **Test with Manual Export**: Trigger a manual export to verify the setup Export Tasks
List ### Viewing Export Details Once you've created an export task, you can view its configuration and status by clicking on the task. The export details page shows: * **Basic Information**: Status, entity type, interval, and connection * **S3 Configuration**: Bucket, region, key prefix, compression, and encryption settings * **Timestamps**: Creation date and last update time Export Details ## Manual Export Manual Export allows you to trigger an immediate export without waiting for the next scheduled execution. This is useful for: * Testing your export configuration * Backfilling historical data * Responding to urgent data requests * Exporting specific time ranges ### How to Trigger a Manual Export Navigate to the export details page and click the "Manual Export" button to trigger a manual export. Manual Export
Modal #### Option 1: Automatic Time Range (Default) Trigger a manual export without specifying time ranges. The system automatically calculates the export window based on the task's interval: **Example - Hourly Task:** * Current time: 10:30 AM * Export window: 10:00 AM to 11:00 AM (current hour) **Example - Daily Task:** * Current date: October 16, 2025 * Export window: October 16, 00:00 to October 17, 00:00 (current day) **API Request:** ```bash theme={null} POST /api/v1/tasks/scheduled/{task_id}/run ``` **Response:** ```json theme={null} { "workflow_id": "task_export_01HK8DBMBN87CY36DEFY62PEXBT", "message": "Manual export triggered successfully", "start_time": "2025-10-16T10:00:00Z", "end_time": "2025-10-16T11:00:00Z", "mode": "manual" } ``` #### Option 2: Custom Time Range Specify exact start and end times for the export: **API Request:** ```bash theme={null} POST /api/v1/tasks/scheduled/{task_id}/run Content-Type: application/json { "start_time": "2025-10-01T00:00:00Z", "end_time": "2025-10-15T23:59:59Z" } ``` **Response:** ```json theme={null} { "workflow_id": "task_export_01HK8DBMBN87CY36DEFY62PEXBT", "message": "Manual export triggered successfully", "start_time": "2025-10-01T00:00:00Z", "end_time": "2025-10-15T23:59:59Z", "mode": "custom" } ``` Custom time ranges must be valid timestamps in RFC3339 format (ISO 8601). The end time must be after the start time. ### Manual Export Behavior * **Independent Execution**: Manual exports don't affect the regular schedule * **Parallel Execution**: Can run alongside scheduled exports * **Tracked Separately**: Appears in the Runs tab with "manual" or "custom" mode * **Same Configuration**: Uses the same S3 settings as the scheduled task * **Immediate Processing**: Starts execution within seconds ## Runs Tab - Execution History The Runs tab displays a comprehensive history of all export executions for a scheduled task, including both automatic scheduled runs and manual exports. Runs History Each run record shows the execution status, time range, file details, and any errors that occurred. Use this to monitor export health and access exported files. ### Downloading Exported Files You can download exported files directly from the Runs tab for any connection type: Go to your scheduled task and click the **Runs** tab to view execution history Find the completed export run you want to download from the list Click the **Download** button next to the run record to download the CSV file. **Benefits:** * ✅ Works with both Flexprice-managed and your own S3 connections * ✅ No S3 console access required * ✅ Download files directly ### Accessing Exported Files Each completed run provides a direct S3 file URL: **File Naming Convention:** ``` {entity_type}-{start_time}-{end_time}.csv ``` **Examples:** ``` invoice-251016100000-251016110000.csv events-251016000000-251017000000.csv.gz credit_usage-251223110000-251223120000.csv ``` **Time Format in Filename:** * `YYMMDDHHMMSS` (Year, Month, Day, Hour, Minute, Second) * Example: `251016100000` = October 16, 2025, 10:00:00 ### Troubleshooting Failed Runs When a run fails, the error summary provides details about what went wrong: **Common Failure Reasons:** | Error | Cause | Solution | | --------------------- | --------------------------------------- | -------------------------------------------------- | | **Connection Failed** | Invalid AWS credentials | Verify and update S3 connection credentials | | **Bucket Not Found** | S3 bucket doesn't exist or wrong region | Check bucket name and region in task configuration | | **Permission Denied** | Insufficient IAM permissions | Review and update IAM policy | | **No Data Found** | No records in the specified time range | Verify the time range or check data availability | | **Timeout** | Export took longer than 15 minutes | Contact support or split into smaller time ranges | ## Best Practices **Use Descriptive Connection Names** Name your S3 connections based on environment and purpose (e.g., "Production Invoice Exports", "Staging Events Backup"). **Organize with Key Prefixes** Use key prefixes to organize exports by environment, data type, and date: * `production/invoices/2025/` * `staging/events/` **Enable Compression for Large Datasets** Use GZIP compression for exports with thousands of records to reduce storage costs by 70-80%. **Choose the Right Interval** * **Hourly**: Real-time analytics, high-frequency reporting * **Daily**: Standard backups, daily batch processing **Test with Manual Export** Always test a new scheduled task with a manual export before relying on automatic execution. **Monitor Execution History** Regularly review the Runs tab to ensure exports are completing successfully and adjust configuration if needed. **Use IAM Best Practices** * Create dedicated IAM users for exports * Grant minimal required permissions * Rotate credentials regularly * Enable CloudTrail for audit logging **Plan for S3 Lifecycle Policies** Configure S3 lifecycle policies to automatically archive or delete old exports: * Transition to S3 Glacier after 90 days * Delete files older than 1 year ## Troubleshooting ### Scheduled Task Not Running **Possible causes:** * Task is disabled * S3 connection is invalid * Temporal workflow service is down **Solution:** 1. Verify the task is enabled in the dashboard 2. Check the S3 connection status 3. Test with a manual export to isolate the issue 4. Review Temporal workflow logs ### Files Not Appearing in S3 **Possible causes:** * Incorrect bucket name or region * Missing IAM permissions * Key prefix typo **Solution:** 1. Verify bucket name matches exactly (case-sensitive) 2. Confirm region matches the bucket's region 3. Review IAM policy and ensure `s3:PutObject` permission 4. Check key prefix for typos or trailing slashes ### Export Timeout Errors **Possible causes:** * Very large time range (months of data) * Thousands of records in a single export * S3 upload speed issues **Solution:** 1. Reduce the time range for custom exports 2. Use hourly intervals instead of daily for high-volume data 3. Enable GZIP compression to reduce upload time 4. Contact support if issue persists ### Invalid Credentials Error **Possible causes:** * AWS credentials expired or rotated * Incorrect Access Key ID or Secret Key * Session token expired (for temporary credentials) **Solution:** 1. Verify credentials in AWS IAM console 2. Update the S3 connection with new credentials 3. For temporary credentials, refresh the session token 4. Ensure the IAM user/role still exists ## API Reference ### Create Scheduled Task ```bash theme={null} POST /api/v1/tasks/scheduled Content-Type: application/json Authorization: Bearer {token} { "connection_id": "conn_01HK8DBMBN87CY36DEFY62PEXBT", "entity_type": "invoice", // Options: "invoice", "events", "credit_topups", "credit_usage" "interval": "daily", "enabled": true, "job_config": { "bucket": "flexprice-exports", "region": "us-west-2", "key_prefix": "invoices/", "endpoint_url": "", // Optional: Custom S3-compatible endpoint "compression": "gzip", "encryption": "AES256" } } ``` ### Update Scheduled Task ```bash theme={null} PATCH /api/v1/tasks/scheduled/{task_id} Content-Type: application/json Authorization: Bearer {token} { "enabled": false } ``` ### Trigger Manual Export ```bash theme={null} POST /api/v1/tasks/scheduled/{task_id}/run Content-Type: application/json Authorization: Bearer {token} { "start_time": "2025-10-01T00:00:00Z", "end_time": "2025-10-15T23:59:59Z" } ``` ### List Scheduled Tasks ```bash theme={null} GET /api/v1/tasks/scheduled?entity_type=invoice&status=published Authorization: Bearer {token} ``` ### Get Task Execution History ```bash theme={null} GET /api/v1/tasks?scheduled_task_id={task_id}&task_type=EXPORT Authorization: Bearer {token} ``` ## Summary S3 Export provides a comprehensive solution for automated data exports to Amazon S3. Key takeaways: * **Four Data Types**: Export invoices, events, credit top-ups, and credit usage data * **Flexible Scheduling**: Hourly or daily automated exports * **Secure Storage**: Multiple encryption options for data at rest * **Manual Export Capability**: Trigger manual exports anytime * **Execution Tracking**: Comprehensive run history and monitoring * **Enterprise-Ready**: Compression, encryption, and scalable architecture * **Easy Integration**: Standard CSV format for downstream processing By implementing S3 Export, you can automate data backups, feed data warehouses, meet compliance requirements, and integrate Flexprice data with your broader data infrastructure. # Event Ingestion API Reference Source: https://docs.flexprice.io/docs/event-ingestion/api-reference Complete API reference for sending events to Flexprice This document provides the complete API reference for sending events to Flexprice. It includes all endpoints, request/response formats, error codes, and examples. ## Base URL ``` https://api.cloud.flexprice.io/v1 ``` ## Authentication All requests require authentication using an API key in the request header: ``` x-api-key: ``` **Security Note**: Keep your API key secure and never expose it in client-side code or public repositories. ## Endpoints ### Single Event Send a single event to Flexprice. **Endpoint**: `POST /events` **Headers**: ``` Content-Type: application/json x-api-key: ``` **Request Body**: ```json theme={null} { "event_name": "string", "external_customer_id": "string", "properties": { "string": "any" }, "event_id": "string", "timestamp": "string", "source": "string" } ``` **Response**: * **Status**: `202 Accepted` * **Body**: ```json theme={null} { "event_id": "string", "message": "Event accepted for processing" } ``` ### Bulk Events Send multiple events in a single request for better performance. **Endpoint**: `POST /events/bulk` **Headers**: ``` Content-Type: application/json x-api-key: ``` **Request Body**: ```json theme={null} { "events": [ { "event_name": "string", "external_customer_id": "string", "properties": { "string": "any" }, "event_id": "string", "timestamp": "string", "source": "string" } ] } ``` **Response**: * **Status**: `202 Accepted` * **Body**: ```json theme={null} { "event_ids": ["string"], "message": "Events accepted for processing" } ``` ## Request Fields ### Required Fields | Field | Type | Description | Example | | ---------------------- | ------ | -------------------------------------------- | --------------- | | `event_name` | string | Must exactly match your feature's Event Name | `"model.usage"` | | `external_customer_id` | string | Your identifier for the customer | `"cust_123"` | ### Conditional Fields | Field | Type | When Required | Description | Example | | -------------------- | ------------- | ---------------------------------- | ------------------------------------ | ------------------- | | `properties` | object | For Sum, Max, Latest, Unique Count | Contains values to aggregate | `{"credits": 2}` | | `properties.` | number/string | When aggregation field is set | Exact field name from feature config | `2` or `"user_123"` | ### Optional Fields | Field | Type | Description | Example | | ----------- | ------ | ------------------------------------ | ---------------------------- | | `event_id` | string | Your unique identifier for the event | `"evt_abc123"` | | `timestamp` | string | ISO 8601 UTC timestamp | `"2025-08-22T07:05:49.441Z"` | | `source` | string | Origin of the event | `"api"`, `"worker"` | ## Data Types ### Property Values * **Numbers**: Use for Sum, Max aggregations ```json theme={null} "properties": { "credits": 2 } ``` * **Strings**: Use for Unique Count, Latest aggregations ```json theme={null} "properties": { "user_id": "user_123" } ``` ### Timestamps * **Format**: ISO 8601 UTC * **Example**: `"2025-08-22T07:05:49.441Z"` * **Default**: Server time if omitted ## Examples ### Basic Event (Count Aggregation) ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/events \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "event_name": "api.calls", "external_customer_id": "cust_123" }' ``` ### Event with Properties (Sum Aggregation) ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/events \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 2 } }' ``` ### Complete Event ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/events \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 2, "model": "gpt-4", "region": "us-east-1" }, "event_id": "evt_abc123", "timestamp": "2025-08-22T07:05:49.441Z", "source": "api" }' ``` ### Bulk Events ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/events/bulk \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "events": [ { "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 2 } }, { "event_name": "model.usage", "external_customer_id": "cust_456", "properties": { "credits": 5 } } ] }' ``` ## Language Examples ### JavaScript (Node.js) ```javascript theme={null} const fetch = require("node-fetch"); async function sendEvent() { const response = await fetch("https://api.cloud.flexprice.io/v1/events", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": process.env.FLEXPRICE_API_KEY, }, body: JSON.stringify({ event_name: "model.usage", external_customer_id: "cust_123", properties: { credits: 2 }, source: "api", }), }); if (response.status === 202) { const result = await response.json(); console.log("Event sent:", result); } else { const error = await response.text(); console.error("Error:", error); } } ``` ### Python ```python theme={null} import os import requests import json def send_event(): url = "https://api.cloud.flexprice.io/v1/events" headers = { "Content-Type": "application/json", "x-api-key": os.environ["FLEXPRICE_API_KEY"] } data = { "event_name": "model.usage", "external_customer_id": "cust_123", "properties": {"credits": 2}, "source": "api" } response = requests.post(url, headers=headers, json=data) if response.status_code == 202: result = response.json() print("Event sent:", result) else: print("Error:", response.text) ``` ### Go ```go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" "os" ) type Event struct { EventName string `json:"event_name"` ExternalCustomerID string `json:"external_customer_id"` Properties map[string]interface{} `json:"properties,omitempty"` Source string `json:"source,omitempty"` } func sendEvent() error { event := Event{ EventName: "model.usage", ExternalCustomerID: "cust_123", Properties: map[string]interface{}{ "credits": 2, }, Source: "api", } jsonData, err := json.Marshal(event) if err != nil { return err } req, err := http.NewRequest("POST", "https://api.cloud.flexprice.io/v1/events", bytes.NewBuffer(jsonData)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("x-api-key", os.Getenv("FLEXPRICE_API_KEY")) client := &http.Client{} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() if resp.StatusCode == 202 { fmt.Println("Event sent successfully") } else { fmt.Printf("Error: HTTP %d\n", resp.StatusCode) } return nil } ``` ### PHP ```php theme={null} 'model.usage', 'external_customer_id' => 'cust_123', 'properties' => [ 'credits' => 2 ], 'source' => 'api' ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'x-api-key: ' . $apiKey ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($httpCode == 202) { echo "Event sent successfully\n"; } else { echo "Error: HTTP $httpCode\n"; echo "Response: $response\n"; } } ``` ## Error Responses ### HTTP Status Codes | Status | Description | | ----------------------- | ------------------------------------------ | | `202 Accepted` | Event accepted for processing | | `400 Bad Request` | Invalid payload or missing required fields | | `401 Unauthorized` | Invalid or missing API key | | `429 Too Many Requests` | Rate limit exceeded | ### Error Response Format ```json theme={null} { "error": "Error description", "details": "Additional error details" } ``` ### Common Error Messages #### Missing Required Field ```json theme={null} { "error": "Missing required field: event_name" } ``` #### Invalid Event Name ```json theme={null} { "error": "Event name 'unknown.event' not found" } ``` #### Invalid Customer ```json theme={null} { "error": "Customer with external_customer_id 'invalid_id' not found" } ``` #### Invalid JSON ```json theme={null} { "error": "Invalid JSON format" } ``` #### Rate Limit Exceeded ```json theme={null} { "error": "Rate limit exceeded. Try again later." } ``` ## Rate Limits * **Single events**: 1000 requests per minute * **Bulk events**: 100 requests per minute (up to 1000 events per request) * **Response**: 429 status code when exceeded ### Rate Limit Headers When approaching rate limits, responses include headers: ``` X-RateLimit-Limit: 1000 X-RateLimit-Remaining: 50 X-RateLimit-Reset: 1640995200 ``` ## Best Practices ### 1. Error Handling Implement proper error handling with retries: ```javascript theme={null} async function sendEventWithRetry(eventData, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await fetch("https://api.cloud.flexprice.io/v1/events", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": process.env.FLEXPRICE_API_KEY, }, body: JSON.stringify(eventData), }); if (response.status === 202) { return await response.json(); } if (response.status === 429) { // Rate limited - wait and retry const retryAfter = response.headers.get("Retry-After") || attempt * 1000; await new Promise((resolve) => setTimeout(resolve, retryAfter)); continue; } throw new Error(`HTTP ${response.status}: ${await response.text()}`); } catch (error) { if (attempt === maxRetries) { throw error; } // Wait before retry await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)); } } } ``` ### 2. Bulk Events for High Volume Use bulk events when sending many events: ```javascript theme={null} const events = [ { event_name: "api.calls", external_customer_id: "cust_1" }, { event_name: "api.calls", external_customer_id: "cust_2" }, // ... more events ]; const response = await fetch("https://api.cloud.flexprice.io/v1/events/bulk", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": process.env.FLEXPRICE_API_KEY, }, body: JSON.stringify({ events }), }); ``` ### 3. Idempotency Use event\_id for idempotency: ```json theme={null} { "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 2 }, "event_id": "unique_event_id_123" } ``` ### 4. Timestamps Include timestamps for historical events: ```json theme={null} { "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 2 }, "timestamp": "2025-08-22T07:05:49.441Z" } ``` ## Validation ### Event Name Validation * Must match a feature's Event Name exactly * Case-sensitive * Cannot be changed after feature creation ### Customer Validation * external\_customer\_id must exist in Flexprice * Customer must be active ### Property Validation * Aggregation field must exist for Sum, Max, Latest, Unique Count * Data types must match aggregation function requirements ## Testing ### Test Endpoint For testing purposes, you can use the same endpoints with test data: ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/events \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "event_name": "test.feature", "external_customer_id": "test_customer", "properties": { "value": 1 } }' ``` ### Validation Checklist Before sending production events: * Feature exists and is active * Event Name matches exactly * Customer exists in Flexprice * Required properties are included * Data types are correct * API key has proper permissions ## Support For API-related issues: 1. Check error messages for specific details 2. Verify your request format matches the examples 3. Test with a simple event first 4. Contact support with specific error details and examples # Connecting to Billing Source: https://docs.flexprice.io/docs/event-ingestion/connecting-to-billing Learn how to connect events to billing and see usage in invoices Events alone don't generate revenue - they need to be connected to pricing plans and customer subscriptions to create billable charges. This guide shows you how to transform your ingested events into actual invoices. ## How Events Flow to Billing The complete flow from events to invoices: 1. **Events** → Usage data transmitted to Flexprice 2. **Features** → Define what you're tracking and how to aggregate 3. **Plans** → Set pricing for features 4. **Subscriptions** → Connect customers to plans 5. **Invoices** → Automatic billing based on usage ## Prerequisites * A metered feature created and configured * Events being transmitted and validated * A customer created in Flexprice ## Step 1: Create a Pricing Plan ### Navigate to Plans 1. Log into your Flexprice Admin Dashboard 2. In the left sidebar, click on **Product Catalog** 3. Click on **Plans** 4. Click **"Create Plan"** ### Configure the Plan 1. **Plan Name**: Give your plan a descriptive name (e.g., "Basic Plan", "Pro Plan") 2. **Description**: Optional description of what the plan includes 3. **Billing Cycle**: Choose how often customers are billed (monthly, annually, etc.) ### Add Your Feature 1. Click **"Add Feature"** or **"Add Charge"** 2. Select your metered feature from the list 3. Configure the pricing: **For Usage-Based Pricing:** * **Price Model**: Choose "Usage Based" * **Price**: Set the price per unit (e.g., \$1 per credit) * **Free Tier**: Optional free usage before billing starts **Example Configuration:** * Feature: Model Usage * Price: \$1.00 per credit * Free Tier: 100 credits per month Plan Configuration ### Advanced Pricing Options **Tiered Pricing:** * First 1000 credits: \$1.00 each * Next 1000 credits: \$0.80 each * Additional credits: \$0.60 each **Package Pricing:** * 1000 credits for $800 (effective $0.80 per credit) **Volume Discounts:** * Set percentage discounts based on usage volume ### Save the Plan Click **"Save Plan"** to create your pricing plan. ## Step 2: Assign Plan to Customer ### Create Subscription 1. Go to **Customers** in the left sidebar 2. Find and click on your customer 3. Click **"Create Subscription"** 4. Select the plan you just created 5. Set the subscription start date 6. Click **"Create Subscription"** Create Subscription ### Verify Subscription After creating the subscription, you should see: * Subscription status: Active * Plan name and billing cycle * Next billing date ## Step 3: Monitor Usage and Billing ### Check Upcoming Invoices 1. Go to the customer's subscription page 2. Click on **"Upcoming Invoices"** tab 3. You'll view a preview of the next invoice with: * Usage quantities from your events * Calculated amounts based on your pricing * Billing period information Upcoming Invoices **Example Invoice Preview:** ``` Subscription Description Interval Quantity Amount Test Feature Usage Based Aug 22 - Sep 22 2 $2 ``` ### Understanding the Billing * **Quantity**: Total usage aggregated from your events * **Amount**: Calculated based on your pricing plan * **Interval**: Current billing period * **Description**: Shows "Usage Based" for metered features ## Real-World Example Let's walk through a complete example: ### 1. Feature Configuration * **Feature Name**: Model Usage * **Event Name**: `model.usage` * **Aggregation**: Sum of `credits` * **Usage Reset**: Periodic (monthly) ### 2. Events Transmitted ```json theme={null} [ { "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 5 } }, { "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 3 } } ] ``` ### 3. Plan Configuration * **Plan Name**: Basic Plan * **Feature**: Model Usage * **Pricing**: \$1.00 per credit * **Free Tier**: 10 credits per month ### 4. Billing Calculation * **Total Usage**: 5 + 3 = 8 credits * **Free Credits**: 10 (no charge for first 10) * **Billable Credits**: 0 (8 \< 10, so no charge) * **Amount**: \$0.00 ### 5. If More Usage Occurs If the customer uses 15 credits total: * **Total Usage**: 15 credits * **Free Credits**: 10 * **Billable Credits**: 5 * **Amount**: \$5.00 ## Advanced Billing Scenarios ### Multiple Features You can add multiple metered features to a single plan: **Plan: Pro Plan** * Model Usage: \$1.00 per credit * API Calls: \$0.01 per call * Storage: \$0.10 per GB **Customer Usage:** * 50 credits consumed * 1000 API calls * 5 GB storage **Invoice:** ``` Model Usage Usage Based Aug 22 - Sep 22 50 $50 API Calls Usage Based Aug 22 - Sep 22 1000 $10 Storage Usage Based Aug 22 - Sep 22 5 $0.50 Total: $60.50 ``` ### Tiered Pricing **Plan: Enterprise Plan** * Model Usage: * First 1000 credits: \$0.80 each * Next 1000 credits: \$0.60 each * Additional: \$0.40 each **Customer Usage: 2500 credits** * First 1000: \$800 * Next 1000: \$600 * Additional 500: \$200 * **Total: \$1600** ## Monitoring and Analytics ### Usage Trends 1. Go to **Usage Tracking** → **Query** 2. Select your feature and customer 3. View usage over time to identify patterns ### Billing Analytics 1. Go to **Invoices** section 2. View historical invoices and revenue 3. Analyze customer usage patterns ## Common Billing Issues ### 1. No Charges Appearing **Possible Causes:** * Feature not added to plan * Customer not subscribed to plan * Usage below free tier * Wrong billing period **Solutions:** * Verify feature is in the plan * Check subscription status * Review free tier settings * Confirm billing cycle dates ### 2. Wrong Amounts **Possible Causes:** * Incorrect pricing configuration * Wrong aggregation values * Billing period mismatch **Solutions:** * Review plan pricing settings * Check event aggregation * Verify billing cycle alignment ### 3. Unexpected Charges **Possible Causes:** * Events transmitted to wrong customer * Incorrect usage reset setting * Duplicate events **Solutions:** * Verify external\_customer\_id * Check Usage Reset configuration * Review event history for duplicates ## Best Practices ### 1. Test Billing Setup Before going live: 1. Create a test customer 2. Transmit a few test events 3. Verify charges appear correctly 4. Check invoice calculations ### 2. Monitor Usage Patterns Regularly check: * Usage trends and spikes * Customer behavior patterns * Revenue impact of pricing changes ### 3. Set Up Alerts Consider alerts for: * Unusual usage spikes * Failed billing attempts * Revenue thresholds ### 4. Document Pricing Keep clear documentation of: * Feature pricing structure * Free tier limits * Billing cycle details * Customer-specific overrides ## Pricing Strategies ### Usage-Based Pricing **Pros:** * Aligns cost with value * Scales automatically * Fair for customers **Cons:** * Revenue can be unpredictable * Complex to forecast ### Tiered Pricing **Pros:** * Encourages higher usage * Predictable revenue at scale * Rewards loyal customers **Cons:** * More complex to configure * Can be confusing for customers ### Hybrid Models Combine multiple pricing approaches: * Base subscription fee * Usage-based charges * Volume discounts ## Next Steps After setting up billing: 1. **Monitor Performance** - Track usage and revenue 2. **Optimize Pricing** - Adjust based on customer behavior 3. **Scale Operations** - Add more features and customers 4. **Automate Processes** - Set up webhooks and integrations ## Troubleshooting ### "No charges in upcoming invoice" * Check that events are being transmitted * Verify feature is in the plan * Confirm customer subscription is active * Review free tier settings ### "Wrong billing amounts" * Verify pricing configuration * Check event aggregation values * Review billing period dates * Confirm usage reset settings ### "Unexpected charges" * Review recent events for the customer * Check for duplicate events * Verify external\_customer\_id mapping * Review plan configuration changes ## Getting Help If you're experiencing billing issues: 1. Check the **[Event Debugger](/docs/event-ingestion/event-debugger)** for event processing status 2. Review your plan configuration carefully 3. Verify customer subscription status 4. Contact support with specific examples and error messages # Creating a Metered Feature Source: https://docs.flexprice.io/docs/event-ingestion/creating-a-metered-feature Step-by-step guide to create a metered feature for event ingestion ## Prerequisites * Access to the Flexprice Admin Dashboard * A clear understanding of what you want to meter (e.g., API calls, storage usage, credits consumed) ## Step-by-Step Guide ### 1. Navigate to Features 1. Log into your Flexprice Admin Dashboard 2. In the left sidebar, click on **Product Catalog** 3. Click on **Features** 4. Click the **"Add Feature"** button ### 2. Basic Feature Information Fill in the basic details: * **Name**: Give your feature a clear, descriptive name (e.g., "Model Usage", "API Calls", "Storage Usage") * **Type**: Select **"Metered"** from the dropdown Create Feature Form ### 3. Configure Event Name **⚠️ CRITICAL: This is the most important setting** The **Event Name** is the unique identifier that connects your events to this feature: * **Must be unique** across all features in your account * **Cannot be changed** after creation * **Must match exactly** what you send in your events * Use lowercase with dots (e.g., `model.usage`, `api.calls`, `storage.gb`) **Example**: If you set Event Name to `model.usage`, every event you transmit must have `"event_name": "model.usage"`. Event Name Configuration ### 4. Choose Aggregation Function Select how you want to measure usage (see [Aggregation Overview](/docs/product-catalogue/features/aggregation/overview) for detailed explanations): * **Count** — Counts the number of events (no property required) * **Sum** — Adds up values from a specific property * **Average** — Calculates the mean value of a specific property * **Count Unique** — Counts unique values of a property * **Latest** — Takes the most recent value sent * **Sum with Multiplier** — Sums values and applies a rate multiplier * **Max** — Takes the highest value seen in the billing period (supports bucketing) * **Weighted Sum** — Time-weighted sum for capacity-based billing **Example**: If you want to track total credits consumed, choose **Sum**. Aggregation Function Selection ### 5. Set Aggregation Field (if required) **⚠️ CRITICAL: This must match your event properties exactly** If you selected Sum, Max, Latest, or Unique Count, you must specify which property to aggregate: * **Field Name**: The exact property key from your events * **Must match case**: `credits` ≠ `Credits` ≠ `CREDITS` * **Must exist**: Every event must include this property **Example**: If you set Aggregation Field to `credits`, every event must include: ```json theme={null} { "properties": { "credits": 2 } } ``` Aggregation Field Configuration ### 6. Configure Usage Reset Choose how usage accumulates over time: * **Periodic** — Usage resets at the start of each billing cycle * Good for: API calls, monthly limits, recurring usage * Example: Customer gets 1000 API calls per month * **Cumulative** — Usage keeps growing across billing cycles * Good for: Storage usage, lifetime metrics * Example: Total storage used since account creation Usage Reset Configuration ### 7. Set Unit Name (Optional) Define how the quantity appears on invoices: * **Singular**: `credit`, `API call`, `GB` * **Plural**: `credits`, `API calls`, `GBs` * Flexprice will automatically choose the correct form **Example**: `credit / credits` will show as "1 credit" or "5 credits" on invoices. ### 8. Add Event Filters (Optional) If you only want to count certain events, add filters: * **Key**: Property name to filter on * **Values**: Allowed values for that property **Example**: Only count events where `region = "EU"` or `model = "gpt-4"`. Event Filters Configuration ### 9. Save the Feature Click **"Save Feature"** to create your metered feature. ## After Creation Once saved, you'll see: * **Feature ID**: A unique identifier for this feature * **Event Name**: The exact string to use in your events * **Aggregation Details**: How the feature processes events **Important**: Note down the **Event Name** and **Aggregation Field** - you'll need these when transmitting events. ## Common Configuration Examples ### API Calls (Count) * **Event Name**: `api.calls` * **Aggregation Function**: Count * **Usage Reset**: Periodic * **Unit Name**: `API call / API calls` ### Credits Consumed (Sum) * **Event Name**: `model.usage` * **Aggregation Function**: Sum * **Aggregation Field**: `credits` * **Usage Reset**: Periodic * **Unit Name**: `credit / credits` ### Storage Usage (Sum) * **Event Name**: `storage.usage` * **Aggregation Function**: Sum * **Aggregation Field**: `gb` * **Usage Reset**: Cumulative * **Unit Name**: `GB` ### Peak Concurrent Users (Max) * **Event Name**: `concurrent.users` * **Aggregation Function**: Max * **Aggregation Field**: `count` * **Usage Reset**: Periodic * **Unit Name**: `user / users` ## ⚠️ Critical Warnings ### Event Name * **Cannot be changed** after creation * Must be **unique** across all features * **Case sensitive** — `api.calls` ≠ `API.calls` ### Aggregation Field * **Must match exactly** the property name in your events * **Case sensitive** — `credits` ≠ `Credits` * **Required** for Sum, Max, Latest, and Unique Count functions ### Testing * Always test your feature configuration before transmitting production events * Use the **[Event Debugger](/docs/event-ingestion/event-debugger)** to validate your setup ## Related Documentation * **[Features Overview](/docs/product-catalogue/features/overview)** — Learn about different feature types * **[Aggregation Functions](/docs/product-catalogue/features/aggregation/overview)** — Understand how data is processed ## Next Steps Now that you have a metered feature, you're ready to: 1. **[Send Events](/docs/event-ingestion/sending-events)** - Learn how to transmit usage data 2. **[Validate Events](/docs/event-ingestion/validating-events)** - Ensure events are being processed correctly 3. **[Connect to Billing](/docs/event-ingestion/connecting-to-billing)** - Set up pricing and subscriptions ## Troubleshooting ### "Events not showing up" * Check that Event Name matches exactly * Verify Aggregation Field exists in your event properties * Ensure the customer exists with the correct `external_customer_id` ### "Wrong aggregation values" * Verify Aggregation Field name matches your event properties * Check that property values are the correct data type (numbers for Sum/Max) * Confirm Usage Reset setting matches your expectations ### "Cannot change Event Name" * Event Name is immutable after creation * Create a new feature with the correct name * Update your application to transmit events with the new name For more detailed troubleshooting, see our **[Troubleshooting Guide](/docs/event-ingestion/troubleshooting)**. # Event Debugger Source: https://docs.flexprice.io/docs/event-ingestion/event-debugger Monitor and debug event processing in real-time The Event Debugger provides visibility into your event processing pipeline, helping you track events, identify issues, and ensure accurate billing. ## Overview When you send events to Flexprice, they go through multiple processing steps—validation, meter matching, aggregation, and billing. The Event Debugger lets you see this entire journey and quickly identify any issues. ## Accessing the Event Debugger Navigate to **Usage Tracking** → **Events Debugger** in your Flexprice dashboard. Event Debugger ## Viewing Events ### Event Stream The Event Debugger displays a real-time stream of events entering your system. Each event shows: * Event ID * Event Name * Customer Lookup Key * Source * Timestamp ### Event Details Click on any event to view complete details: ```json theme={null} { "id": "event_01", "external_customer_id": "cust-new", "event_name": "api_calls", "timestamp": "2025-10-15T11:38:47.962Z", "properties": {}, "source": "onboarding", "environment_id": "env_10" } ``` ## Sorting Events Click the Sort button to order events by: * Name * Email * Created At * Updated At The sort order can be ascending or descending. ## Filtering Events Use the Filter button to narrow down events. You can combine multiple filters: ### Filter by Event ID Search for a specific event by its ID: ``` event_id contains "123" ``` ### Filter by Event Name Filter by event type: ``` event_name contains "api_calls" ``` ### Filter by Customer Find events for a specific customer: ``` external_customer_id contains "cust_new" ``` ### Filter by Time Range Find events after a specific time period using Start Time: **Start Time (After):** ``` start_time after "October 1st, 2025" ``` Find events before a specific time period using End Time: **End Time (Before):** ``` end_time before "October 10th, 2025" ``` ### Filter by Source Filter events by their source: ``` source contains "onboarding" ``` You can apply multiple filters simultaneously and click "Add filter" to combine conditions. ## Common Use Cases ### Verify Events Are Being Received After setting up event ingestion, check the Event Debugger to confirm events are arriving: 1. Navigate to the Events Debugger 2. Look for your recent events in the list 3. Click on an event to view its full details ### Debug Missing Events If expected events aren't showing up: 1. Check that `event_name` matches your metered feature exactly 2. Verify `external_customer_id` is correct 3. Confirm the timestamp format is valid ISO 8601 4. Check the source parameter matches your expected value ### Find Events for a Specific Customer To review all events for a customer: 1. Click the Filter button 2. Add a filter: External Customer ID contains "your\_customer\_id" 3. Click Apply to see filtered results ### Review Events in a Time Period To see events within a specific date range: 1. Click the Filter button 2. Set Start Time (After) to your beginning date 3. Set End Time (Before) to your ending date 4. Apply the filters ## Best Practices **Test Event Integration Early** Send test events and verify they appear in the debugger before going to production. **Monitor Regularly** Check the debugger periodically to ensure events are being received correctly. **Verify After Changes** After updating metered features or plans, use the debugger to confirm events still process correctly. **Use Filters Effectively** Combine multiple filters to quickly find specific events you need to review. ## Related Documentation * [Creating a Metered Feature](/docs/event-ingestion/creating-a-metered-feature) * [Sending Events](/docs/event-ingestion/sending-events) * [Validating Events](/docs/event-ingestion/validating-events) * [Troubleshooting](/docs/event-ingestion/troubleshooting) ## Need Help? If events aren't appearing as expected: 1. Review the [Sending Events](/docs/event-ingestion/sending-events) guide to verify your integration 2. Check the [Validating Events](/docs/event-ingestion/validating-events) documentation 3. See the [Troubleshooting](/docs/event-ingestion/troubleshooting) guide for common issues # Event Monitoring Source: https://docs.flexprice.io/docs/event-ingestion/monitoring Monitor the health and performance of your event ingestion and processing pipeline The Event Monitoring endpoint helps you track event ingestion and processing in real-time, allowing you to identify bottlenecks and ensure events are flowing correctly through your system. ## API Endpoint ``` GET https://api.cloud.flexprice.io/v1/events/monitoring ``` ### Authentication Include your API key in the request header: ``` x-api-key: ``` ### Request Parameters This endpoint accepts no query parameters. It automatically monitors the last 24 hours of data for your tenant and environment. ### Response ```json theme={null} { "total_count": 15432, "consumption_lag": 245, "post_processing_lag": 78 } ``` **Response Fields:** * `total_count` - Total distinct events stored in ClickHouse in the last 24 hours * `consumption_lag` - Kafka consumer lag for event processing (number of unprocessed messages) * `post_processing_lag` - Kafka consumer lag for feature usage tracking (number of unprocessed messages) ## Related Documentation * [Sending Events](/docs/event-ingestion/sending-events) * [Event Debugger](/docs/event-ingestion/event-debugger) * [Troubleshooting](/docs/event-ingestion/troubleshooting) # Overview Source: https://docs.flexprice.io/docs/event-ingestion/overview Learn how to send usage events to Flexprice for metered billing Event ingestion is the core mechanism that powers Flexprice's metered billing system. It enables you to transmit usage data from your application to Flexprice, which then automatically calculates charges and generates invoices based on your pricing plans. ## What is Event Ingestion? When you have a **Metered Feature** in Flexprice (such as API calls, storage usage, or credits consumed), you need to inform Flexprice when and how much your customers are utilizing that feature. This is accomplished by transmitting **events** - JSON payloads that describe usage activity. ## How It Works 1. **[Create a Metered Feature](/docs/event-ingestion/creating-a-metered-feature)** - Define what you want to track (e.g., "model.usage", "api.calls") 2. **[Send Events](/docs/event-ingestion/sending-events)** - Your application transmits usage data to Flexprice via API 3. **[Validate Events](/docs/event-ingestion/validating-events)** - Verify that events are being processed correctly 4. **[Connect to Billing](/docs/event-ingestion/connecting-to-billing)** - Link the feature to a pricing plan and customer subscription 5. **Automatic Invoicing** - Flexprice calculates charges and generates invoices ## Key Concepts ### Event A JSON payload containing usage information: * **Who used the feature** (`external_customer_id`) * **What feature was used** (`event_name`) * **How much was used** (`properties`) * **When it happened** (`timestamp`) ### Aggregation How Flexprice combines multiple events into a single quantity for billing (see [Aggregation Overview](/docs/product-catalogue/features/aggregation/overview) for detailed explanations): * **Count** - Number of events * **Sum** - Total of a numeric property * **Average** - Mean value of a numeric property * **Count Unique** - Number of distinct values * **Latest** - Most recent value * **Sum with Multiplier** - Sum with rate conversion * **Max** - Highest value seen (supports bucketing) * **Weighted Sum** - Time-weighted sum for capacity billing ### Usage Reset How usage accumulates over time: * **Periodic** - Resets each billing cycle (e.g., monthly API calls) * **Cumulative** - Keeps growing (e.g., total storage used) ## Quick Start Follow these steps to get started with event ingestion: 1. **[Create Your First Metered Feature](/docs/event-ingestion/creating-a-metered-feature)** - Set up what you want to track 2. **[Send Your First Event](/docs/event-ingestion/sending-events)** - Learn how to send usage data 3. **[Validate and Debug](/docs/event-ingestion/validating-events)** - Ensure events are working correctly 4. **[Connect to Billing](/docs/event-ingestion/connecting-to-billing)** - Turn usage into invoices ## Common Use Cases * **API Usage** - Track API calls, requests, or tokens consumed * **Storage** - Monitor file storage, database usage, or bandwidth * **AI/ML** - Count model inferences, training hours, or data processed * **User Activity** - Track logins, feature usage, or time spent * **Compute Resources** - Monitor CPU time, memory usage, or GPU hours ## What You'll Learn This guide will walk you through the complete event ingestion workflow, from setting up your first metered feature to viewing charges on customer invoices. By the end, you'll understand: * How to configure metered features correctly * The exact format for sending events * How to verify that events are being processed * How usage flows into billing and invoicing * Common pitfalls and how to avoid them ## Related Documentation * **[Features Overview](/docs/product-catalogue/features/aggregation/overview)** - Learn about different feature types * **[Plans](/docs/product-catalogue/plans/overview)** - Understand pricing plan configuration * **[Subscriptions](/docs/subscriptions/customers-create-subscription)** - Manage customer subscriptions * **[Invoices](/docs/invoices/overview)** - View and manage billing ## Next Steps Ready to get started? Begin with **[Creating a Metered Feature](/docs/event-ingestion/creating-a-metered-feature)**. If you encounter any issues, check our **[Troubleshooting Guide](/docs/event-ingestion/troubleshooting)** for common solutions. # Sending Events Source: https://docs.flexprice.io/docs/event-ingestion/sending-events Learn how to send usage events to Flexprice via API Once you have created a metered feature, you can start transmitting events to Flexprice. Events are JSON payloads that describe usage activity and are transmitted via HTTP POST requests. ## Prerequisites * A metered feature created in Flexprice * Your API key from the Admin Dashboard * The Event Name from your feature configuration ## API Endpoints ### Single Event ``` POST https://api.cloud.flexprice.io/v1/events ``` ### Bulk Events ``` POST https://api.cloud.flexprice.io/v1/events/bulk ``` ## Authentication Include your API key in the request header: ``` x-api-key: ``` **Important**: Keep your API key secure and never expose it in client-side code. ## Event Payload Structure ### Required Fields | Field | Type | Description | Example | | ---------------------- | ------ | -------------------------------------------- | --------------- | | `event_name` | string | Must exactly match your feature's Event Name | `"model.usage"` | | `external_customer_id` | string | Your identifier for the customer | `"cust_123"` | ### Conditional Fields | Field | Type | When Required | Description | | -------------------- | ------------- | ---------------------------------- | ---------------------------------------------------- | | `properties` | object | For Sum, Max, Latest, Unique Count | Contains the values to aggregate | | `properties.` | number/string | When aggregation field is set | The exact field name from your feature configuration | ### Optional Fields | Field | Type | Description | Example | | ----------- | ------ | ------------------------------------ | ---------------------------- | | `event_id` | string | Your unique identifier for the event | `"evt_123"` | | `timestamp` | string | ISO 8601 UTC timestamp | `"2025-08-22T07:05:49.441Z"` | | `source` | string | Origin of the event | `"api"`, `"worker"` | ## Single Event Examples ### Basic Event (Count Aggregation) If your feature uses **Count** aggregation, you only need the required fields: ```json theme={null} { "event_name": "api.calls", "external_customer_id": "cust_123" } ``` ### Event with Properties (Sum Aggregation) If your feature uses **Sum** aggregation with field `credits`: ```json theme={null} { "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 2 } } ``` ### Complete Event Example ```json theme={null} { "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 2, "model": "gpt-4", "region": "us-east-1" }, "event_id": "evt_abc123", "timestamp": "2025-08-22T07:05:49.441Z", "source": "api" } ``` ## cURL Examples ### Single Event ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/events \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "event_name": "model.usage", "external_customer_id": "cust-test-customer", "properties": { "credits": 2 } }' ``` cURL Event Ingestion Example ### Response ```json theme={null} { "event_id": "event_01K389J4M1F1NZG6XP0AMD6J52", "message": "Event accepted for processing" } ``` Postman Event Ingestion Example ## Bulk Events Transmit multiple events in a single request for better performance: ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/events/bulk \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "events": [ { "event_name": "model.usage", "external_customer_id": "cust-test-customer", "properties": { "credits": 2 } }, { "event_name": "model.usage", "external_customer_id": "cust-another-customer", "properties": { "credits": 5 } } ] }' ``` ## Language Examples ### JavaScript (Node.js) ```javascript theme={null} const fetch = require("node-fetch"); async function sendEvent() { const response = await fetch("https://api.cloud.flexprice.io/v1/events", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": process.env.FLEXPRICE_API_KEY, }, body: JSON.stringify({ event_name: "model.usage", external_customer_id: "cust-test-customer", properties: { credits: 2 }, source: "api", }), }); const result = await response.json(); console.log("Event transmitted:", result); } ``` ### Python ```python theme={null} import os import requests import json def send_event(): url = "https://api.cloud.flexprice.io/v1/events" headers = { "Content-Type": "application/json", "x-api-key": os.environ["FLEXPRICE_API_KEY"] } data = { "event_name": "model.usage", "external_customer_id": "cust-test-customer", "properties": {"credits": 2}, "source": "api" } response = requests.post(url, headers=headers, json=data) result = response.json() print("Event transmitted:", result) ``` ### Go ```go theme={null} package main import ( "bytes" "encoding/json" "fmt" "net/http" "os" ) type Event struct { EventName string `json:"event_name"` ExternalCustomerID string `json:"external_customer_id"` Properties map[string]interface{} `json:"properties,omitempty"` Source string `json:"source,omitempty"` } func sendEvent() error { event := Event{ EventName: "model.usage", ExternalCustomerID: "cust-test-customer", Properties: map[string]interface{}{ "credits": 2, }, Source: "api", } jsonData, err := json.Marshal(event) if err != nil { return err } req, err := http.NewRequest("POST", "https://api.cloud.flexprice.io/v1/events", bytes.NewBuffer(jsonData)) if err != nil { return err } req.Header.Set("Content-Type", "application/json") req.Header.Set("x-api-key", os.Getenv("FLEXPRICE_API_KEY")) client := &http.Client{} resp, err := client.Do(req) if err != nil { return err } defer resp.Body.Close() fmt.Printf("Event transmitted with status: %d\n", resp.StatusCode) return nil } ``` ### PHP ```php theme={null} 'model.usage', 'external_customer_id' => 'cust-test-customer', 'properties' => [ 'credits' => 2 ], 'source' => 'api' ]; $ch = curl_init(); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_POST, true); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'x-api-key: ' . $apiKey ]); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); $response = curl_exec($ch); $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); echo "Event transmitted with status: $httpCode\n"; echo "Response: $response\n"; } ``` ## Data Types and Best Practices ### Property Values * **Numbers**: Use for Sum, Max aggregations ```json theme={null} "properties": { "credits": 2 } ``` * **Strings**: Use for Unique Count, Latest aggregations ```json theme={null} "properties": { "user_id": "user_123" } ``` ### Timestamps * Use ISO 8601 format in UTC * Example: `"2025-08-22T07:05:49.441Z"` * If omitted, server time is used ### Event IDs * Optional but recommended for traceability * Should be unique within your system * Helps with debugging and idempotency ## Common Patterns ### API Usage Tracking ```json theme={null} { "event_name": "api.calls", "external_customer_id": "cust_123", "properties": { "endpoint": "/v1/chat", "method": "POST", "response_time": 150 }, "source": "api" } ``` ### Storage Usage ```json theme={null} { "event_name": "storage.usage", "external_customer_id": "cust_123", "properties": { "gb": 1.5, "bucket": "user-uploads" }, "source": "storage-service" } ``` ### User Activity ```json theme={null} { "event_name": "user.login", "external_customer_id": "cust_123", "properties": { "user_id": "user_456", "platform": "web" }, "source": "auth-service" } ``` ## Error Handling ### HTTP Status Codes * **202 Accepted**: Event accepted for processing * **400 Bad Request**: Invalid payload or missing required fields * **401 Unauthorized**: Invalid or missing API key * **429 Too Many Requests**: Rate limit exceeded ### Common Errors **Missing required field** ```json theme={null} { "error": "Missing required field: event_name" } ``` **Invalid event\_name** ```json theme={null} { "error": "Event name 'unknown.event' not found" } ``` **Invalid customer** ```json theme={null} { "error": "Customer with external_customer_id 'invalid_id' not found" } ``` ## Rate Limits * **Single events**: 1000 requests per minute * **Bulk events**: 100 requests per minute (up to 1000 events per request) * **Response**: 429 status code when exceeded ## Best Practices ### 1. Validate Your Configuration Before transmitting production events: * Verify Event Name matches your feature exactly * Confirm Aggregation Field exists in your events * Test with a small number of events first ### 2. Handle Failures Gracefully ```javascript theme={null} async function sendEventWithRetry(eventData, maxRetries = 3) { for (let attempt = 1; attempt <= maxRetries; attempt++) { try { const response = await fetch("https://api.cloud.flexprice.io/v1/events", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": process.env.FLEXPRICE_API_KEY, }, body: JSON.stringify(eventData), }); if (response.status === 202) { return await response.json(); } if (response.status === 429) { // Rate limited — wait and retry await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)); continue; } throw new Error(`HTTP ${response.status}: ${await response.text()}`); } catch (error) { if (attempt === maxRetries) { throw error; } // Wait before retry await new Promise((resolve) => setTimeout(resolve, 1000 * attempt)); } } } ``` ### 3. Use Bulk Events for High Volume When transmitting many events, use the bulk endpoint: ```javascript theme={null} const events = [ { event_name: "api.calls", external_customer_id: "cust_1" }, { event_name: "api.calls", external_customer_id: "cust_2" }, // ... more events ]; const response = await fetch("https://api.cloud.flexprice.io/v1/events/bulk", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": process.env.FLEXPRICE_API_KEY, }, body: JSON.stringify({ events }), }); ``` ### 4. Include Relevant Metadata Add useful properties to your events for debugging: ```json theme={null} { "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 2, "request_id": "req_abc123", "version": "1.0" }, "source": "api" } ``` ## Related Documentation * **[API Reference](/docs/event-ingestion/api-reference)** — Complete API documentation * **[Validating Events](/docs/event-ingestion/validating-events)** — Check that events are being processed correctly * **[Troubleshooting](/docs/event-ingestion/troubleshooting)** — Fix common issues ## Next Steps After sending events, you should: 1. **[Validate Events](/docs/event-ingestion/validating-events)** - Verify that events are being processed correctly 2. **[Connect to Billing](/docs/event-ingestion/connecting-to-billing)** - Set up pricing and subscriptions ## Troubleshooting ### "Event not found" error * Check that Event Name matches your feature exactly * Verify the feature exists and is active ### "Customer not found" error * Ensure the customer exists in Flexprice * Check that `external_customer_id` matches exactly ### "Missing required field" error * Verify all required fields are present * Check field names match exactly (case-sensitive) ### Events not appearing in dashboard * Check the **[Event Debugger](/docs/event-ingestion/event-debugger)** for processing status * Verify Event Name and Aggregation Field configuration * Ensure customer has an active subscription with the feature # Troubleshooting Event Ingestion Source: https://docs.flexprice.io/docs/event-ingestion/troubleshooting Comprehensive guide to fixing common event ingestion issues in Flexprice This guide helps you diagnose and fix common issues with event ingestion in Flexprice. Follow the systematic approach below to identify and resolve problems. ## Quick Diagnostic Checklist Before diving into specific issues, run through this checklist: * Feature exists and is active * Event Name matches exactly (case-sensitive) * External Customer ID exists in Flexprice * API key is valid and has proper permissions * Events are being transmitted to the correct endpoint * Required fields are present in event payload * Aggregation field exists in event properties (if required) * Customer has an active subscription with the feature ## Common Issues and Solutions ### 1. Events Not Appearing in Dashboard **Symptoms**: Events transmitted successfully (202 response) but don't show up in Events dashboard. **Possible Causes and Solutions:** #### A. Invalid Event Name **Cause**: Event Name in your event doesn't match the feature configuration. **Check**: 1. Go to **Product Catalog** → **Features** 2. Find your feature and note the exact Event Name 3. Compare with what you're transmitting in events **Fix**: ```json theme={null} // ❌ Wrong { "event_name": "Model.Usage", "external_customer_id": "cust_123" } // ✅ Correct { "event_name": "model.usage", "external_customer_id": "cust_123" } ``` #### B. Customer Doesn't Exist **Cause**: The external\_customer\_id doesn't exist in Flexprice. **Check**: 1. Go to **Customers** section 2. Search for your external\_customer\_id 3. Verify the customer exists and is active **Fix**: * Create the customer first, or * Use the correct external\_customer\_id #### C. Feature Not Active **Cause**: The feature exists but is not active. **Check**: 1. Go to **Product Catalog** → **Features** 2. Find your feature 3. Verify status is "Active" **Fix**: * Activate the feature if it's archived ### 2. Wrong Aggregation Values **Symptoms**: Events appear but billing shows incorrect quantities. **Possible Causes and Solutions:** #### A. Aggregation Field Name Mismatch **Cause**: Property name in events doesn't match the Aggregation Field. **Check**: 1. Go to your feature configuration 2. Note the exact Aggregation Field name 3. Compare with your event properties **Fix**: ```json theme={null} // Feature config: Aggregation Field = "credits" // ❌ Wrong { "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "tokens": 2 // Wrong field name } } // ✅ Correct { "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 2 // Correct field name } } ``` #### B. Wrong Data Type **Cause**: Using strings instead of numbers for numeric aggregation. **Check**: * Numeric aggregations (SUM, AVERAGE, SUM WITH MULTIPLIER, MAX, WEIGHTED SUM) require numbers * COUNT UNIQUE can use strings or numbers * LATEST can use strings or numbers * COUNT doesn't use properties **Fix**: ```json theme={null} // ❌ Wrong (for Sum aggregation) { "properties": { "credits": "2" // String instead of number } } // ✅ Correct { "properties": { "credits": 2 // Number } } ``` #### C. Missing Properties **Cause**: Events don't include the required aggregation field. **Check**: * Every event must include the aggregation field * Check for typos in property names **Fix**: ```json theme={null} // ❌ Wrong (missing properties for Sum aggregation) { "event_name": "model.usage", "external_customer_id": "cust_123" // Missing properties object } // ✅ Correct { "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 2 } } ``` ### 3. No Charges in Billing **Symptoms**: Events are processed correctly but no charges appear in invoices. **Possible Causes and Solutions:** #### A. Feature Not in Plan **Cause**: The feature exists but isn't added to any pricing plan. **Check**: 1. Go to **Product Catalog** → **Plans** 2. Find the plan your customer is subscribed to 3. Verify the feature is included **Fix**: * Add the feature to the plan with appropriate pricing #### B. Customer Not Subscribed **Cause**: Customer exists but doesn't have an active subscription. **Check**: 1. Go to **Customers** → Find your customer 2. Check subscription status 3. Verify the subscription includes the feature **Fix**: * Create a subscription for the customer * Ensure the subscription includes the feature #### C. Usage Below Free Tier **Cause**: Usage is within the free tier limit. **Check**: 1. Review plan configuration 2. Check free tier settings 3. Verify usage quantities **Fix**: * Adjust free tier limits if needed * Transmit more usage to exceed free tier #### D. Wrong Billing Period **Cause**: Events are in a different billing period than expected. **Check**: 1. Review subscription billing cycle 2. Check event timestamps 3. Verify billing period dates **Fix**: * Wait for the correct billing period * Check upcoming invoices for the right period ### 4. API Errors **Symptoms**: Getting error responses when transmitting events. #### A. 400 Bad Request **Common Causes**: * Missing required fields * Invalid JSON format * Invalid field values **Debug**: ```bash theme={null} # Check your payload curl -v --request POST \ --url https://api.cloud.flexprice.io/v1/events \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 2 } }' ``` **Common Fixes**: ```json theme={null} // ❌ Missing required field { "external_customer_id": "cust_123" // Missing event_name } // ✅ Complete payload { "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 2 } } ``` #### B. 401 Unauthorized **Cause**: Invalid or missing API key. **Fix**: 1. Verify your API key is correct 2. Check API key permissions 3. Ensure the key is active #### C. 429 Too Many Requests **Cause**: Rate limit exceeded. **Fix**: * Implement exponential backoff * Use bulk events for high volume * Reduce request frequency ### 5. Unexpected Usage Resets **Symptoms**: Usage resets when it shouldn't. #### A. Wrong Usage Reset Setting **Check**: 1. Go to your feature configuration 2. Review Usage Reset setting: * **Periodic**: Resets each billing cycle * **Cumulative**: Keeps growing **Fix**: * Change Usage Reset setting if needed * Note: This may require creating a new feature #### B. Subscription Changes **Cause**: Subscription was modified or recreated. **Check**: 1. Review subscription history 2. Check for plan changes 3. Verify billing cycle alignment ### 6. Duplicate Events **Symptoms**: Same event counted multiple times. **Causes**: * Retrying failed requests * Multiple event sources * Application bugs **Solutions**: * Use event\_id for idempotency * Implement proper retry logic * Check for duplicate event sources ## Systematic Debugging Approach ### Step 1: Verify Event Ingestion 1. Send a test event 2. Check API response (should be 202 Accepted) 3. Look for the event in Events dashboard 4. Verify the payload is correct ### Step 2: Check Feature Configuration 1. Confirm Event Name matches exactly 2. Verify Aggregation Function is correct 3. Check Aggregation Field name 4. Review Usage Reset setting ### Step 3: Validate Customer Setup 1. Ensure customer exists 2. Check external\_customer\_id matches 3. Verify subscription is active 4. Confirm plan includes the feature ### Step 4: Review Billing Configuration 1. Check plan pricing settings 2. Verify free tier configuration 3. Review billing cycle dates 4. Check upcoming invoices ## Debugging Tools ### Event Debugger Use the Event Debugger for real-time insights: 1. Go to **Usage Tracking** → **Event Debugger** 2. Monitor events as they're processed 3. Look for errors or warnings ### Events Dashboard Check the Events dashboard for: 1. Event processing status 2. Complete event payloads 3. Timestamp information 4. Customer mapping ### Query Tool Use the Query tool to: 1. View aggregated usage 2. Check billing period alignment 3. Verify aggregation calculations ## Testing Your Fix After implementing a fix: 1. **Send Test Events** ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/events \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "event_name": "model.usage", "external_customer_id": "cust-test-customer", "properties": { "credits": 1 } }' ``` 2. **Verify Event Processing** * Check Events dashboard * Confirm payload is correct * Verify aggregation values 3. **Check Billing Impact** * Review upcoming invoices * Confirm charges appear correctly * Verify amounts are accurate ## Prevention Best Practices ### 1. Test Before Production * Always test with a small number of events * Use test customers and features * Verify the complete flow end-to-end ### 2. Monitor Regularly * Check Events dashboard daily * Review usage patterns weekly * Monitor billing accuracy monthly ### 3. Use Consistent Naming * Standardize Event Names across your system * Use consistent external\_customer\_id format * Follow naming conventions for properties ### 4. Implement Proper Error Handling * Handle API errors gracefully * Implement retry logic with backoff * Log failed events for debugging ### 5. Document Your Setup * Keep records of feature configurations * Document Event Names and field mappings * Maintain customer ID mappings ## Getting Additional Help If you're still experiencing issues: 1. **Gather Information**: * Event payload examples * Feature configuration screenshots * Error messages and timestamps * Customer and subscription details 2. **Check Documentation**: * Review relevant guides * Check API reference * Look for similar issues 3. **Contact Support**: * Provide specific error details * Include relevant screenshots * Share event examples (with sensitive data removed) ## Common Configuration Examples ### API Usage Tracking ```json theme={null} // Feature: API Calls (Count) { "event_name": "api.calls", "external_customer_id": "cust_123" } // Feature: API Usage (Sum of tokens) { "event_name": "api.usage", "external_customer_id": "cust_123", "properties": { "tokens": 150 } } ``` ### Storage Usage ```json theme={null} // Feature: Storage (Sum of GB) { "event_name": "storage.usage", "external_customer_id": "cust_123", "properties": { "gb": 1.5 } } ``` ### User Activity ```json theme={null} // Feature: Active Users (Unique Count) { "event_name": "active.users", "external_customer_id": "cust_123", "properties": { "user_id": "user_456" } } ``` Remember: The key to successful event ingestion is attention to detail. Small differences in naming, data types, or configuration can cause significant issues. Always test thoroughly and monitor continuously. # Validating Events Source: https://docs.flexprice.io/docs/event-ingestion/validating-events Learn how to verify that events are being processed correctly in Flexprice After transmitting events to Flexprice, it's crucial to verify that they're being processed correctly. This guide shows you how to check event ingestion, debug issues, and ensure your billing calculations are accurate. ## Prerequisites * Events have been transmitted to Flexprice * Access to the Flexprice Admin Dashboard * Knowledge of your feature configuration ## Checking Event Ingestion ### 1. Navigate to Events Dashboard 1. Log into your Flexprice Admin Dashboard 2. In the left sidebar, click on **Developers** 3. Click on **[Event Debugger](https://admin.flexprice.io/usage-tracking/events?page=1)** You'll see a list of all ingested events with key information: * **Event ID**: Unique identifier for each event * **Event Name**: The feature being tracked * **External Customer ID**: Customer associated with the event * **Source**: Origin of the event (if provided) * **Timestamp**: When the event occurred Events Dashboard ### 2. View Event Details Click on any event in the list to view the complete payload that was transmitted: Event Details Modal This displays the exact JSON payload that Flexprice received, including: * All properties transmitted with the event * Timestamp information * Customer details * Any additional metadata **Important**: Verify that the `properties` object contains the correct aggregation field with the expected value. ## Using the Event Debugger The Event Debugger provides real-time insights into event processing: 1. Go to **Usage Tracking** → **Event Debugger** 2. You can see: * Events being processed in real-time * Any errors or warnings * Processing status and timing This is especially useful for debugging issues with event ingestion. ## Validating Aggregation ### Check Feature Configuration 1. Go to **Product Catalog** → **Features** 2. Find your metered feature 3. Verify the configuration: * **Event Name** matches what you're transmitting * **Aggregation Function** is set correctly * **Aggregation Field** exists in your event properties ### Verify Event Properties In the Events dashboard, check that: 1. **Event Name** matches your feature exactly 2. **Properties** contain the aggregation field 3. **Property values** are the correct data type **Example**: If your feature aggregates `credits`, ensure every event has: ```json theme={null} { "properties": { "credits": 2 } } ``` ## Common Validation Issues ### 1. Events Not Appearing **Possible Causes:** * Invalid Event Name * Missing required fields * Customer doesn't exist * API key issues **Solutions:** * Check Event Name spelling and case * Verify all required fields are present * Ensure customer exists in Flexprice * Validate API key permissions ### 2. Wrong Aggregation Values **Possible Causes:** * Aggregation field name mismatch * Incorrect data types * Missing properties **Solutions:** * Verify field name matches exactly (case-sensitive) * Use numbers for Sum/Max, strings for Unique Count * Ensure every event includes the required property ### 3. Events Appearing but No Billing **Possible Causes:** * Feature not added to a plan * Customer not subscribed to the plan * Usage reset configuration **Solutions:** * Add feature to a pricing plan * Subscribe customer to the plan * Check Usage Reset setting (Periodic vs Cumulative) ## Testing Your Setup ### Step-by-Step Validation 1. **Transmit a Test Event** ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/events \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "event_name": "model.usage", "external_customer_id": "cust-test-customer", "properties": { "credits": 1 } }' ``` 2. **Check Event Ingestion** * Go to Events dashboard * Locate your test event * Verify the payload is correct 3. **Verify Aggregation** * Check that the event appears in the correct billing period * Verify the aggregation value is as expected 4. **Test Billing Impact** * Ensure feature is in a plan * Subscribe customer to the plan * Check upcoming invoices ## Debugging Checklist Use this checklist to systematically debug event ingestion issues: ### ✅ Event Configuration * Feature exists and is active * Event Name matches exactly * Aggregation Function is correct * Aggregation Field is set (if required) ### ✅ Event Payload * All required fields present * Event Name matches feature * External Customer ID exists * Properties contain aggregation field * Property values are correct data type ### ✅ Customer Setup * Customer exists in Flexprice * External Customer ID matches * Customer has active subscription * Subscription includes the feature ### ✅ Billing Configuration * Feature is added to a plan * Plan has pricing for the feature * Customer is subscribed to the plan * Usage Reset setting is correct ## Advanced Validation ### Query Events by Customer You can filter events by customer to see all usage for a specific account: 1. In the Events dashboard, use the filter options 2. Filter by External Customer ID 3. Review all events for that customer ### Check Aggregated Usage 1. Go to **Usage Tracking** → **Query** 2. Select your feature 3. Choose the customer and time period 4. View aggregated usage values This displays how Flexprice is calculating the total usage for billing. Query Tab Event Preview ### Validate Billing Periods For **Periodic** usage reset: * Usage should reset at the start of each billing cycle * Check that usage accumulates correctly within the period For **Cumulative** usage reset: * Usage should keep growing across billing cycles * Verify no unexpected resets ## Troubleshooting Common Scenarios ### Scenario 1: Events Transmitted but Not Counted **Symptoms**: Events appear in dashboard but don't affect billing **Check**: 1. Feature configuration (Event Name, Aggregation Field) 2. Event properties (correct field name and data type) 3. Customer subscription status 4. Plan configuration ### Scenario 2: Wrong Aggregation Values **Symptoms**: Billing shows incorrect quantities **Check**: 1. Aggregation Field name matches exactly 2. Property values are correct data type 3. No duplicate or conflicting events 4. Usage Reset configuration ### Scenario 3: Events Missing from Dashboard **Symptoms**: Events not visible in Events list **Check**: 1. API response status (should be 202 Accepted) 2. Event Name validity 3. Customer existence 4. API key permissions ### Scenario 4: Unexpected Usage Resets **Symptoms**: Usage resets when it shouldn't **Check**: 1. Usage Reset setting (Periodic vs Cumulative) 2. Billing cycle configuration 3. Subscription changes 4. Feature configuration changes ## Best Practices for Validation ### 1. Test Before Production Always test your event ingestion setup with a small number of events before going live. ### 2. Monitor Regularly Check the Events dashboard regularly to ensure events are being processed correctly. ### 3. Use Consistent Identifiers Use consistent Event Names and External Customer IDs across your system. ### 4. Include Debugging Information Add useful properties to your events for debugging: ```json theme={null} { "event_name": "model.usage", "external_customer_id": "cust_123", "properties": { "credits": 2, "request_id": "req_abc123", "version": "1.0" }, "source": "api" } ``` ### 5. Set Up Alerts Consider setting up alerts for: * Failed event ingestion * Unusual usage patterns * Missing events ## Next Steps After validating your events: **[Connect to Billing](/docs/event-ingestion/connecting-to-billing)** - Set up pricing and subscriptions ## Getting Help If you're still experiencing issues after following this validation guide: 1. Check the **[Event Debugger](/docs/event-ingestion/event-debugger)** for detailed error messages 2. Review your feature configuration carefully 3. Test with a simple event payload 4. Contact support with specific error messages and event examples # Architecture Source: https://docs.flexprice.io/docs/getting-started/architecture How Flexprice is engineered for scale, reliability, and data integrity — the infrastructure, data flows, failure modes, and recovery guarantees behind the platform. Flexprice is a usage metering, pricing, and billing engine that sits between your application and your payment providers. You stream usage events in, configure how each event, feature, or model is priced, and Flexprice handles everything downstream — metering, credit balances, entitlements, invoicing, and settlement — then plugs into your gateway of choice. This document describes how the platform is built: its components, how data moves through them, what happens when something fails, and how the system recovers. It is written for the engineers and architects who need to satisfy themselves that Flexprice can carry production billing traffic. ## Design principles The architecture is shaped by a small set of decisions that hold across every layer. Every billing outcome is derived from raw usage events. Those events are persisted redundantly and retained for replay, so any downstream state can be rebuilt from first principles. No single component failure causes data loss. Each stage of the pipeline has an independent durable store that absorbs the failure of the stage after it. Every component is region-restricted. Data for a region never leaves it, and each region runs an independent stack. The same artifacts that run Flexprice Cloud ship as Helm charts. Every dependency is open source and swappable, so a dedicated deployment is the cloud architecture, not a fork. ## System architecture The platform is fully containerized. A single codebase runs in three modes — **API**, **Consumer**, and **Background Worker** — behind a load balancer, with a private data tier (multi-AZ) and a set of external components for analytics, orchestration, and webhook delivery. Only the API service is exposed to the internet. It sits behind an Application Load Balancer and a WAF in public subnets; every database, broker, and cache lives in private subnets, across multiple availability zones, with no inbound internet route. Flexprice system architecture: customer infrastructure sends events via SDK or the Flexprice collector through an Application Load Balancer and WAF into multi-AZ private subnets running the API, Consumer, and Background Worker services alongside MSK, RDS, ElastiCache, DynamoDB, and S3, with ClickHouse, Temporal, and Svix as external components. ### Runtime services All three services are the same image, started in different roles. This keeps deployment, versioning, and operational tooling uniform. | Service | Responsibility | | --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **API** | The only internet-facing component. Authenticates and validates requests, serves the 200+ REST endpoints, writes transactional state to PostgreSQL, and publishes events to Kafka. | | **Consumer** | Reads from Kafka and processes events asynchronously — usage ingestion, enrichment against PostgreSQL, writes to ClickHouse, alerting, and webhook fan-out. | | **Background Worker** | Executes durable, long-running workflows on Temporal: billing cycles, scheduled jobs, retries, and multi-step operations that require state and guaranteed completion. | ### Data stores Each store is chosen for one job and isolated to it. Every required component has an open-source equivalent that ships in the self-hosted charts. The one managed-cloud service below — DynamoDB — is optional and used only in Flexprice Cloud. | Store | Role | Why this choice | | ------------------------------------------------ | ---------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **PostgreSQL** (RDS) | System of record: customers, subscriptions, plans, pricing, entitlements, invoices, audit log. | Strong consistency and transactional integrity for configuration and financial state. | | **ClickHouse** | Event store and analytics engine: raw events, enriched events, aggregations. | Column-oriented OLAP built for high-volume ingestion and sub-second aggregation over billions of rows. | | **Kafka** (MSK) | Event backbone between API and Consumer. Multi-broker, multi-AZ. | Decouples ingestion from processing, buffers traffic spikes, and guarantees ordered, replayable delivery. | | **Redis** (ElastiCache) | Hot-path cache for balances and frequently read configuration. | Sub-millisecond reads that keep latency-sensitive checks off the primary databases. | | **DynamoDB** *(Flexprice Cloud only — optional)* | An additional ingestion-redundancy buffer for replay and recovery. | The most resilient redundancy layer in Flexprice Cloud: a fully managed, always-available key-value sink that survives even when the rest of the pipeline is degraded. It is not part of the core architecture — self-hosted and dedicated deployments run without it. | | **S3** | Invoice PDFs, generated reports, scheduled exports, and long-term event archival. | Cheap, durable object storage for artifacts and cold data. | DynamoDB is the only managed-cloud component in the architecture, and it is entirely optional. Flexprice uses it in Flexprice Cloud as the most resilient form of data-redundancy store. Self-hosted and dedicated deployments do not include it and require no cloud-specific component — durability is already guaranteed by Kafka's replayable log, the SDK's retries, and the S3 degraded-mode fallback. ## Event ingestion pipeline Ingestion is the most infrastructure-heavy part of the system, because it is the part that must never lose data. Everything downstream — balances, invoices, analytics, reconciliation — is reconstructable as long as the events survive. ### Ingestion modes You choose how events reach Flexprice based on how your systems are already built. Server-side SDKs in all popular languages send events directly. The SDK runs in sync mode with configurable retries and fallback handling built in. A Flexprice collector runs inside your infrastructure, pulls from your existing event bus, applies custom transformations to your internal format, and forwards to Flexprice. For systems that prefer to call Flexprice directly, every ingestion path is also a plain authenticated REST endpoint. ### Ingestion flow The receive path is deliberately lightweight. The API performs only static validation — a well-formed payload on an authenticated endpoint — then writes the event to Kafka, the durable backbone, before acknowledging. In Flexprice Cloud the API additionally writes to DynamoDB as an optional redundancy buffer; self-hosted deployments rely on Kafka's replayable log alone. Heavier work (enrichment, aggregation, ClickHouse writes) happens asynchronously off the Kafka stream, so a spike in volume never slows the acknowledgement path. Flexprice event ingestion pipeline: the SDK or collector posts to /events async with retries; on success the request passes the WAF to the API service, which dual-writes each event to DynamoDB and to MSK before the Consumer enriches and writes to ClickHouse. When retries are exhausted, events are written to an S3 bucket and replayed by a retry job. On the consumer side, events land in ClickHouse twice: a **raw events** table that is the immutable base for replay and reconciliation, and a **processed events** table where each event is enriched with the customer, subscription, feature, meter, price, and line item it maps to. Every event is traceable end to end, down to the exact entities it was billed against. ## Reliability and failure modes The pipeline is designed so that the failure of any one component degrades gracefully and loses nothing. Each stage is backed by an independent durable store that absorbs the failure of the stage after it. | Scenario | Behavior | Recovery | | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Kafka unavailable** | The SDK's retries and the S3 degraded-mode fallback hold the events; in Flexprice Cloud they are also persisted to DynamoDB. The API surfaces the failure rather than dropping it silently. | Once Kafka recovers, replay jobs drain the buffered events — the S3 fallback in any deployment, plus DynamoDB in Flexprice Cloud — back into the pipeline. | | **ClickHouse unavailable** | Events accumulate in Kafka; the consumer pauses. | The consumer resumes and replays the backlog from Kafka when ClickHouse returns. | | **Flexprice fully unreachable** | After SDK retries are exhausted, an optional degraded mode writes each event to a customer-owned S3 bucket, keyed by event ID with the exact payload. | Server-to-server retry jobs read the bucket and re-ingest every event, then clear it. | | **Duplicate delivery** | Events are idempotent on event ID, so retries and replays converge to the same state. | No manual intervention — deduplication is intrinsic. | | **Bad data from upstream** | Raw events are retained untouched, separate from processed state. | Affected events can be corrected and replayed from the retained history. | ### Data recovery and replay Because events are the currency of the system, they are retained well beyond their processing lifetime. In Flexprice Cloud, events held in DynamoDB are retained for up to one year and then archived to S3, giving **point-in-time replay** across the entire window; self-hosted deployments achieve the same replay from Kafka's retained log and S3 archival. If any downstream store is lost or corrupted, it can be rebuilt by replaying the retained events — no derived state is ever the only copy of anything. The degraded-mode S3 fallback and its retry jobs are an opt-in, per-customer configuration deployed for enterprise workloads. It requires granting Flexprice server-to-server read access to the bucket. ## Real-time balances and alerting The most latency-sensitive question in usage billing is *does this customer have balance to perform this action?* Flexprice answers it without forcing you onto its critical path. ### How balances are computed Balances are never stored as a separate mutable number — they are derived from usage in ClickHouse. Every incoming event is rolled up into materialized views and pre-aggregated tables, so the current balance is a fast aggregation query rather than a running counter that can drift. The **fetch-balance API** lets the caller decide the freshness it needs. Rather than a fixed server-side TTL, the caller specifies a maximum acceptable age per request: if the cached value is within that age it is returned immediately from cache; if it is staler, the value is recomputed from ClickHouse. Critical surfaces — the billing page, the customer portal — always read the live value. ### Push-based alerts Most customers never query Flexprice in their hot path at all. Every event enqueues a per-customer aggregation that fires at most **once per customer per minute**, and that single trigger drives: * Low-balance alerts * Auto top-ups * Entitlement-exhaustion alerts These are pushed back to your event bus — API, Kafka, SNS, or SMS — as they happen. The common pattern is for customers to maintain a simple `has_balance` flag per customer in their own Redis, updated from these alerts, and gate actions on that flag. **Flexprice is never in the critical path of the decision.** ### Freshness guarantees | Tier | Guarantee | How | | -------------------------- | ------------------------------------ | -------------------------------------------------------------------------------------------------------------- | | **Standard** | Balances reconciled within 5 minutes | A fallback cron sweeps the trailing 5-minute window and triggers alerts. | | **Enterprise (dedicated)** | Sub-minute SLA, tuned to requirement | Achieved by scaling ingestion parallelism and ClickHouse compute — the two levers that set end-to-end latency. | ## Service-level agreements Flexprice publishes explicit availability and latency targets for the API service — the only internet-facing component — measured continuously and reported per region. Two operations carry their own latency objectives because they sit in the customer's hot path: checking entitlement and wallet balance, and reporting usage measurements. ### Availability | Plan | Uptime SLA | Maximum downtime / month | | ---------------------- | ---------- | ------------------------ | | Standard and Premium | 99.95% | ≈ 21.9 minutes | | Enterprise (dedicated) | 99.99% | ≈ 4.4 minutes | Availability is measured per region in five-minute intervals as `1 − (failed requests ÷ total requests)` against the API service, aggregated over a calendar month. Failed requests are server-side `5xx` responses originating from Flexprice. The calculation excludes scheduled maintenance announced in advance, errors caused by client misuse (`4xx`, invalid payloads, throttling), and disruption outside Flexprice's control. The contractual SLA and any associated service credits are defined in your agreement. ### Latency targets | Operation | Objective | Measured at | | --------------------------------------------- | ----------------- | -------------------------------------------------------------------- | | Entitlement / balance check | **P95 \< 500 ms** | API service (server-side), per region, excluding client network time | | Usage measurement reporting (event ingestion) | **P95 ≈ 200 ms** | API service (server-side), per region, excluding client network time | The balance check is the path most customers gate actions on. The ingestion target reflects the lightweight receive path described above: the API acknowledges after the durable write and defers enrichment off the Kafka stream. Response latency is distinct from balance freshness — how recently usage is reflected in the returned number — which is governed by the [freshness tiers](#freshness-guarantees) above, and can be tightened to sub-minute on enterprise dedicated deployments. ## Multi-region and data residency The architecture is multi-region by default, with stacks in **US, India, and EU**. Every component in a region is restricted to that region, and the managed dependencies are configured against the matching regional cloud. Data for a region is processed and stored only within it, which lets enterprise deployments satisfy residency requirements without bespoke engineering. ## Observability The entire platform is OpenTelemetry-native and streams both traces and logs. You can point it at your own OTel-compatible provider, so Flexprice telemetry lands alongside the rest of your stack rather than in a silo. For enterprise deployments, the internal dashboards are shared as exportable definitions so you start with the same operational view the Flexprice team uses. ## Analytics and reconciliation Billing systems live or die on whether their numbers can be independently verified. Flexprice exposes its data at several levels so you can reconcile however you prefer. ClickHouse (real-time event data) and PostgreSQL (subscriptions, invoices, configuration) are exposed over read-only connections to a BI tool such as Metabase, giving you full SQL access to build any reconciliation or analytics workflow. A summarized customer-level view — usage, wallet balance, and the subscription, price, meter, feature, and line item every figure derives from — available out of the box without standing up a BI stack. The same API powers the built-in customer portal. Hourly exports of processed event rows, fully enriched with their meter, feature, and price mappings, delivered to your S3 as CSV or JSON for ingestion into your own systems. Every entity — meters, prices, features, customers, and more — exposes complete CRUD APIs. Any workflow Flexprice runs internally can be rebuilt on your side. Invoices act as immutable checkpoints: each generated invoice snapshots aggregate usage and charges per customer and feature, so historical periods never require re-scanning the full event history. In parallel, every state change — entity created, updated, deleted — is written to a system-events audit table, streamed through Kafka, and delivered as webhooks via Svix to whatever endpoints you subscribe. ## Deployment models The same architecture is delivered three ways, with no divergence in code between them. Fully managed, multi-region SaaS. Flexprice operates the entire stack. A single-tenant deployment in your own infrastructure, operated to an agreed SLA. Identical architecture, isolated to you. Deploy with public Helm charts on Kubernetes, or on ECS. Open-source dependencies are bundled and can be swapped for your existing managed services. Releases are versioned and tagged, so upgrades are explicit and reversible. Because every deployment model runs the same containers against the same dependencies, what is validated in the cloud is exactly what runs in a dedicated or self-hosted environment. # Flexprice Cloud Source: https://docs.flexprice.io/docs/getting-started/cloud Learn how to quickly get started with Flexprice Cloud - the fastest way to integrate usage-based pricing ## Quick Setup Getting started with Flexprice Cloud is straightforward and requires no infrastructure setup. Follow these simple steps: 1. Visit [Flexprice Cloud](https://admin.flexprice.io/auth) 2. Sign up using either: * Google account * Email and password That's it! Your Flexprice Cloud instance is ready to use. ## Understanding Environments When you first log in, you'll be placed in the **Sandbox** environment. Flexprice provides different environments to help you manage your pricing across different stages: A pre-configured environment with Cursor pricing template for you to experiment and learn Your live environment for real customer data and billing Environments ## Exploring the Sample Setup The Sandbox environment comes pre-configured with Cursor's pricing model, giving you a practical example of how to structure complex pricing: Explore the detailed guide on how Cursor's pricing is implemented in Flexprice ## Setting up API Keys To integrate Flexprice with your application, you'll need to generate an API key: 1. Navigate to the **Developers** tab API Keys Page 2. Click **Add** to create a new API key Create API Key 3. Configure your key: * **Name**: Give it a descriptive name (e.g., "Development") * **Permissions**: Select "Read & Write" * **Expiration**: Choose "Never" for long-term use 4. Click **Create** Created API Key 5. **Important**: Copy your API key immediately - it will only be shown once! ### Using Your API Key Include your API key in all requests to Flexprice API: ```bash theme={null} curl -X POST https://api.cloud.flexprice.io/v1/customers \ -H "x-api-key: YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{"name": "Example Customer", "external_id": ""}' ``` Keep your API key secure and never expose it in client-side code or public repositories. ## Need Help? If you need assistance or have questions: * Email us at [support@flexprice.io](mailto:support@flexprice.io) # Configuration Source: https://docs.flexprice.io/docs/getting-started/configuration Configure your self-hosted Flexprice instance ## Configuration Methods Flexprice can be configured using: 1. Environment variables (recommended) 2. Configuration files 3. Command-line arguments (for specific overrides) ## Environment Variables Environment variables are the recommended way to configure Flexprice. All environment variables are prefixed with `FLEXPRICE_`. Here's a comprehensive list of available variables: ### Deployment Configuration | Variable | Description | Default | Required | | --------------------------- | ------------------------------------------------------------------------------------------------- | ------- | -------- | | `FLEXPRICE_DEPLOYMENT_MODE` | Deployment mode (local, api, consumer, temporal\_worker, aws\_lambda\_api, aws\_lambda\_consumer) | `local` | No | | `FLEXPRICE_SERVER_ADDRESS` | Server address and port | `:8080` | No | ### Authentication Configuration | Variable | Description | Default | Required | | ------------------------------------- | --------------------------------------------- | ----------- | ---------------------- | | `FLEXPRICE_AUTH_PROVIDER` | Authentication provider (flexprice, supabase) | `flexprice` | No | | `FLEXPRICE_AUTH_SECRET` | Secret key for authentication | - | Yes | | `FLEXPRICE_AUTH_SUPABASE_BASE_URL` | Supabase base URL (if using Supabase auth) | - | Only if using Supabase | | `FLEXPRICE_AUTH_SUPABASE_SERVICE_KEY` | Supabase service key | - | Only if using Supabase | | `FLEXPRICE_AUTH_API_KEY_HEADER` | Header name for API key authentication | `x-api-key` | No | | `FLEXPRICE_AUTH_API_KEY_KEYS` | JSON string of API keys configuration | - | No | ### Database Configuration | Variable | Description | Default | Required | | --------------------------------- | --------------------------------------- | -------------- | -------- | | `FLEXPRICE_POSTGRES_HOST` | PostgreSQL host | `localhost` | No | | `FLEXPRICE_POSTGRES_PORT` | PostgreSQL port | `5432` | No | | `FLEXPRICE_POSTGRES_USER` | PostgreSQL username | `flexprice` | No | | `FLEXPRICE_POSTGRES_PASSWORD` | PostgreSQL password | `flexprice123` | Yes | | `FLEXPRICE_POSTGRES_DBNAME` | PostgreSQL database name | `flexprice` | No | | `FLEXPRICE_POSTGRES_SSLMODE` | PostgreSQL SSL mode (disable, require) | `disable` | No | | `FLEXPRICE_POSTGRES_AUTO_MIGRATE` | Automatically run migrations on startup | `false` | No | ### ClickHouse Configuration | Variable | Description | Default | Required | | ------------------------------- | --------------------------------- | ---------------- | -------- | | `FLEXPRICE_CLICKHOUSE_ADDRESS` | ClickHouse address (host:port) | `localhost:9000` | No | | `FLEXPRICE_CLICKHOUSE_TLS` | Use TLS for ClickHouse connection | `false` | No | | `FLEXPRICE_CLICKHOUSE_USERNAME` | ClickHouse username | `flexprice` | No | | `FLEXPRICE_CLICKHOUSE_PASSWORD` | ClickHouse password | `flexprice123` | Yes | | `FLEXPRICE_CLICKHOUSE_DATABASE` | ClickHouse database name | `flexprice` | No | ### Kafka Configuration | Variable | Description | Default | Required | | -------------------------------- | ------------------------------------------- | -------------------------- | ----------------------- | | `FLEXPRICE_KAFKA_BROKERS` | Kafka brokers (comma-separated) | `localhost:29092` | No | | `FLEXPRICE_KAFKA_CONSUMER_GROUP` | Kafka consumer group ID | `flexprice-consumer-local` | No | | `FLEXPRICE_KAFKA_TOPIC` | Kafka topic for events | `events` | No | | `FLEXPRICE_KAFKA_USE_SASL` | Use SASL authentication for Kafka | `false` | No | | `FLEXPRICE_KAFKA_SASL_MECHANISM` | SASL mechanism (PLAIN, SCRAM-SHA-256, etc.) | - | Only if SASL is enabled | | `FLEXPRICE_KAFKA_SASL_USER` | SASL username | - | Only if SASL is enabled | | `FLEXPRICE_KAFKA_SASL_PASSWORD` | SASL password | - | Only if SASL is enabled | | `FLEXPRICE_KAFKA_CLIENT_ID` | Kafka client ID | `flexprice-client-local` | No | ### Temporal Configuration | Variable | Description | Default | Required | | --------------------------------- | ------------------------------- | -------------------- | ---------------------------- | | `FLEXPRICE_TEMPORAL_ADDRESS` | Temporal service address | `localhost:7233` | No | | `FLEXPRICE_TEMPORAL_TLS` | Use TLS for Temporal connection | `false` | No | | `FLEXPRICE_TEMPORAL_NAMESPACE` | Temporal namespace | `default` | No | | `FLEXPRICE_TEMPORAL_TASK_QUEUE` | Temporal task queue | `billing-task-queue` | No | | `FLEXPRICE_TEMPORAL_API_KEY` | Temporal API key | - | Only if using Temporal Cloud | | `FLEXPRICE_TEMPORAL_API_KEY_NAME` | Temporal API key name | - | Only if using Temporal Cloud | ### Logging and Monitoring | Variable | Description | Default | Required | | ------------------------------ | ---------------------------------------- | ------------- | ------------------------- | | `FLEXPRICE_LOGGING_LEVEL` | Logging level (debug, info, warn, error) | `info` | No | | `FLEXPRICE_SENTRY_ENABLED` | Enable Sentry error reporting | `false` | No | | `FLEXPRICE_SENTRY_DSN` | Sentry DSN | - | Only if Sentry is enabled | | `FLEXPRICE_SENTRY_ENVIRONMENT` | Sentry environment | `development` | No | | `FLEXPRICE_SENTRY_SAMPLE_RATE` | Sentry sampling rate (0.0-1.0) | `1.0` | No | ### DynamoDB Configuration | Variable | Description | Default | Required | | ------------------------------------- | ------------------------------ | ----------- | --------------------------- | | `FLEXPRICE_DYNAMODB_IN_USE` | Use DynamoDB for event storage | `false` | No | | `FLEXPRICE_DYNAMODB_REGION` | AWS region for DynamoDB | `us-east-1` | Only if DynamoDB is enabled | | `FLEXPRICE_DYNAMODB_EVENT_TABLE_NAME` | DynamoDB table name for events | `events` | Only if DynamoDB is enabled | ### Webhook Configuration | Variable | Description | Default | Required | | ------------------------------------ | ---------------------------------------- | ------- | -------- | | `FLEXPRICE_WEBHOOK_MAX_RETRIES` | Maximum number of webhook retry attempts | `3` | No | | `FLEXPRICE_WEBHOOK_INITIAL_INTERVAL` | Initial retry interval (e.g., 1s) | `1s` | No | | `FLEXPRICE_WEBHOOK_MAX_INTERVAL` | Maximum retry interval (e.g., 10s) | `10s` | No | | `FLEXPRICE_WEBHOOK_MULTIPLIER` | Backoff multiplier for retries | `2.0` | No | | `FLEXPRICE_WEBHOOK_MAX_ELAPSED_TIME` | Maximum total retry time (e.g., 2m) | `2m` | No | ### Event Publishing | Variable | Description | Default | Required | | ------------------------------------- | --------------------------------------------------- | ------- | -------- | | `FLEXPRICE_EVENT_PUBLISH_DESTINATION` | Event publishing destination (kafka, dynamodb, all) | `kafka` | No | ## Using a .env File For local development, you can use a `.env` file to set environment variables. Create a file named `.env` in the root directory of your project: ```bash theme={null} # Deployment Configuration FLEXPRICE_DEPLOYMENT_MODE=local FLEXPRICE_SERVER_ADDRESS=":8080" # Authentication Configuration FLEXPRICE_AUTH_PROVIDER="flexprice" FLEXPRICE_AUTH_SECRET="your_secure_secret_key" FLEXPRICE_AUTH_API_KEY_HEADER="x-api-key" # Database Configuration FLEXPRICE_POSTGRES_HOST=localhost FLEXPRICE_POSTGRES_PORT=5432 FLEXPRICE_POSTGRES_USER=flexprice FLEXPRICE_POSTGRES_PASSWORD=flexprice123 FLEXPRICE_POSTGRES_DBNAME=flexprice FLEXPRICE_POSTGRES_SSLMODE=disable FLEXPRICE_POSTGRES_AUTO_MIGRATE=true # ClickHouse Configuration FLEXPRICE_CLICKHOUSE_ADDRESS=localhost:9000 FLEXPRICE_CLICKHOUSE_TLS=false FLEXPRICE_CLICKHOUSE_USERNAME=flexprice FLEXPRICE_CLICKHOUSE_PASSWORD=flexprice123 FLEXPRICE_CLICKHOUSE_DATABASE=flexprice # Kafka Configuration FLEXPRICE_KAFKA_BROKERS=localhost:29092 FLEXPRICE_KAFKA_CONSUMER_GROUP=flexprice-consumer-local FLEXPRICE_KAFKA_TOPIC=events # Logging Configuration FLEXPRICE_LOGGING_LEVEL=debug ``` When using Docker Compose, you can specify environment variables in your `docker-compose.yml` file or use the `.env` file directly. ## Configuration File For more complex configurations, you can use a YAML configuration file. By default, Flexprice looks for a file named `config.yaml` in the `internal/config` directory: ```yaml theme={null} deployment: mode: "local" # "local", "docker", "production" server: address: ":8080" auth: provider: "flexprice" # "flexprice" or "supabase" secret: "your_secure_secret_key" supabase: base_url: "http://localhost:54321" service_key: "" api_key: header: "x-api-key" keys: "your_api_key_hash": tenant_id: "00000000-0000-0000-0000-000000000000" user_id: "00000000-0000-0000-0000-000000000000" name: "Dev API Keys" is_active: true kafka: brokers: "localhost:29092" consumer_group: "flexprice-consumer-local" topic: "events" use_sasl: false sasl_mechanism: "" sasl_user: "" sasl_password: "" client_id: "flexprice-client-local" clickhouse: address: "localhost:9000" tls: false username: "flexprice" password: "flexprice123" database: "flexprice" postgres: host: "localhost" port: 5432 user: "flexprice" password: "flexprice123" dbname: "flexprice" sslmode: "disable" auto_migrate: false temporal: address: "localhost:7233" tls: false namespace: "default" task_queue: "billing-task-queue" ``` Environment variables take precedence over configuration file settings. If an environment variable is set, it will override the corresponding value in the configuration file. ## Production Best Practices When configuring Flexprice for production, follow these best practices: ### Security 1. **Use strong, unique passwords** for all database users 2. **Enable SSL/TLS** for all external connections 3. **Set up a firewall** to restrict access to your servers 4. **Use a secure API key** for authentication 5. **Rotate secrets regularly** to maintain security ### Performance 1. **Allocate sufficient resources** to each component based on your expected load 2. **Monitor resource usage** and scale as needed 3. **Configure appropriate Kafka settings** for your message volume 4. **Adjust database connection pools** based on your workload ### High Availability 1. **Set up database replication** for PostgreSQL and ClickHouse 2. **Deploy multiple API server instances** behind a load balancer 3. **Configure Kafka with multiple brokers** in a cluster 4. **Implement automated backups** for all data stores # Go-Live Checklist Source: https://docs.flexprice.io/docs/getting-started/go-live-checklist Complete this before switching to Production. ## 1. Environment & API Access * Switch to the Production environment in the Flexprice dashboard. * Generate a Production API key (Read & Write) and copy it immediately. * Confirm auth header is `x-api-key: ` and base URL matches your region: `https://us.api.flexprice.io` (US) or `https://api.cloud.flexprice.io` (India). * Revoke Sandbox API keys for any system now pointing to Production. ## 2. Product Catalog * Clone your catalog from Sandbox via the Plans dashboard and verify it transferred correctly. * Confirm all metered feature Event Names are active in Production and match exactly what your app sends. They are immutable after creation. ## 3. Event Ingestion * Verify required fields on every event: `event_name` (case-sensitive), `external_customer_id`, `timestamp` (ISO 8601 UTC), `event_id` (unique). * Send test events and confirm they appear in the Event Debugger. An `event_name` mismatch returns HTTP 202 but is never counted toward billing. No error is thrown. ## 4. Customers & Subscriptions * Create Production customers and confirm `external_customer_id` matches what your event pipeline sends. * Create subscriptions on the correct Production plans. ## 5. Invoicing * Review `subscription_config.grace_period_days` and confirm the auto-cancel behavior is correct. * Decide auto-collect vs. manual invoicing before your first billing cycle runs. * Generate a test invoice and verify line items match expected usage. ## 6. Webhooks * Register Production webhook endpoints and implement signature verification. * Subscribe to: `invoice.update.finalized`, `invoice.update.payment`, `subscription.created`, `subscription.cancelled`. * Return 2xx for all received events. A 4xx permanently stops retries for that event. ## 7. Smoke Test 1. Send test events for a Production test customer. 2. Confirm meter aggregation matches what you sent. 3. Finalize the draft invoice and verify line items. 4. Confirm `invoice.update.finalized` fires and your handler processes it. ## 8. Post-Launch * Subscribe to the [Flexprice changelog](/changelog) for API changes. * Subscribe to the [status page](https://status.flexprice.io) for incident notifications. * Join the [Slack community](https://join.slack.com/t/flexpricecommunity/shared_invite/zt-39uat51l0-n8JmSikHZP~bHJNXladeaQ) for support. # Self-hosting on AWS Source: https://docs.flexprice.io/docs/getting-started/self-hosting-aws Complete guide to deploy Flexprice on AWS with ECS, Aurora PostgreSQL, MSK, EKS, and Redis This guide provides a comprehensive, step-by-step walkthrough for self-hosting Flexprice on **AWS** in a production-ready setup. It covers VPC networking, ECS compute (EC2 with ARM64), Aurora PostgreSQL, Amazon MSK (Kafka), EKS with ClickHouse, ElastiCache Redis, DynamoDB, IAM, secrets management, and observability. ## Prerequisites Before you begin, ensure you have the following: An [AWS account](https://aws.amazon.com/) with administrator or equivalent permissions to create VPCs, ECS, RDS, MSK, EKS, S3, IAM roles, and CloudWatch resources [AWS CLI v2](https://aws.amazon.com/cli/) installed and configured with credentials (`aws configure`) [Docker](https://www.docker.com/) installed (for building and pushing images to ECR) [kubectl](https://kubernetes.io/docs/tasks/tools/) installed (for EKS/ClickHouse management) [eksctl](https://eksctl.io/) installed (optional but recommended for EKS cluster creation) [Helm](https://helm.sh/) installed (for ClickHouse deployment) ### Region selection Choose an AWS region that: * Has all required services (see Cost estimation for the list) * Is geographically close to your users for lower latency * Meets your compliance requirements (e.g., GDPR for EU data) This guide uses `us-east-1` as the example region. Replace with your preferred region. ### Cost estimation We provide two configurations: a **development** setup for testing and a **production** setup for high-throughput workloads (100M+ events/month). | Component | Configuration | Monthly Cost | | ----------------------- | ----------------------------------------------------------------- | ------------------- | | EC2 for ECS | 10x m6g.xlarge (ARM64/Graviton) | \~\$1,030 | | Aurora PostgreSQL | 2x db.r8g.xlarge (Writer + Reader) | \~\$650 | | Amazon MSK | 2 brokers, kafka.m5.large (4 vCPU, 8 GB), 1 TB storage per broker | \~\$350 | | EKS + ClickHouse | Control plane + m5.8xlarge nodes | \~\$1,900 | | ElastiCache Redis | Multi-node cluster (cache.r6g.large, cluster mode) | \~\$650 | | DynamoDB | On-demand, \~100M events | \~\$50 | | Storage (EBS) | 3,000 GB across components (gp3) | \~\$290 | | ALB + NAT Gateway | 2x NAT for HA | \~\$130 | | S3, CloudWatch, Secrets | Storage + logs | \~\$50 | | **AWS Subtotal** | | **\~\$5,100** | | Third-party services | Temporal Cloud, Supabase, Svix, Grafana | \~\$400 | | **Total** | | **\~\$5,500/month** | | Component | Configuration | Monthly Cost | | --------------------- | ------------------------------ | --------------------- | | ECS Fargate | 3 tasks (0.5 vCPU, 1 GB each) | \~\$80 | | RDS PostgreSQL | db.t3.small, Single-AZ | \~\$30 | | Amazon MSK | 2x kafka.t3.small, 100 GB each | \~\$90 | | EKS + ClickHouse | 2x m5.large nodes | \~\$200 | | ElastiCache Redis | cache.t3.micro | \~\$15 | | NAT Gateway | 1 gateway | \~\$35 | | ALB + S3 + CloudWatch | Standard | \~\$50 | | **Total** | | **\~\$500-600/month** | Costs vary by region and usage. Use the [AWS Pricing Calculator](https://calculator.aws/) for accurate estimates. ARM64/Graviton instances provide \~20% cost savings over x86. ### Sizing for 100M events/month | Component | Development | Production (100M events/month) | | ------------------- | ------------------------- | ------------------------------------------ | | ECS API | 1 task, 0.5 vCPU, 1 GB | 6 tasks, 0.75 vCPU, 1.5 GB each | | ECS Consumer | 1 task, 0.5 vCPU, 1 GB | 30 tasks, 1 vCPU, 1.75 GB each | | ECS Temporal Worker | 1 task, 1 vCPU, 2 GB | 3 tasks, 2 vCPU, 4 GB each | | Database | RDS db.t3.small | Aurora 2x db.r8g.xlarge | | Kafka | 2x kafka.t3.small, 100 GB | 2 brokers, kafka.m5.large, 1 TB per broker | | ClickHouse | 2x m5.large (8 GB) | m5.8xlarge node(s) | | Redis | cache.t3.micro | cache.r6g.large, multi-node cluster mode | **Traffic and storage estimates:** * 100M events/month = \~38.5 events/second average * Peak traffic: 150-200 events/second (4-5x burst) * ClickHouse storage: \~50 GB/month growth * DynamoDB: \~20 GB/month growth *** ## Architecture overview Flexprice on AWS runs with the following production architecture: AWS architecture for Flexprice **Data flow:** * **Clients** → **Cloudflare** (DNS, WAF, rate limiting) → **ALB** → **ECS** (API, Consumer, Temporal Worker) * **API** writes to **Aurora PostgreSQL**, publishes events to **MSK (Kafka)** and **DynamoDB** * **Consumer** reads from Kafka and writes to **ClickHouse** (on EKS) for analytics * **Temporal Worker** connects to **Temporal Cloud** for workflow orchestration * **ElastiCache Redis** provides caching in cluster mode * **S3** stores invoice PDFs; **CloudWatch** and **Grafana Cloud** collect logs and metrics This guide uses **Temporal Cloud** (recommended for production). You can also self-host Temporal, but it requires additional infrastructure. Cloudflare is optional but recommended for DNS and WAF. ### Component summary | Component | AWS Service | Purpose | | ------------------ | ------------------ | -------------------------------------------- | | Compute | ECS on EC2 (ARM64) | API, Consumer, Temporal Worker services | | Primary Database | Aurora PostgreSQL | Transactional data, subscriptions, customers | | Analytics Database | ClickHouse on EKS | Event analytics, usage aggregation | | Message Queue | Amazon MSK | Event streaming between services | | Cache | ElastiCache Redis | Session cache, rate limiting | | Event Store | DynamoDB | Durable event storage | | Object Storage | S3 | Invoice PDFs, exports | | Workflow Engine | Temporal Cloud | Billing workflows, scheduled jobs | | Authentication | Supabase | User authentication (optional) | | Webhooks | Svix | Webhook delivery (optional) | *** ## Step 1: VPC and networking Create a VPC with public and private subnets across two Availability Zones for high availability. Unless otherwise specified, create each resource in this guide via AWS Console, CLI, or IaC using the configuration described in the tables. ### VPC configuration | Setting | Value | Purpose | | ------------------------- | ------------------------------------ | ------------------------------ | | VPC CIDR | `10.0.0.0/16` | 65,536 IP addresses | | Availability Zones | 2 (e.g., `us-east-1a`, `us-east-1b`) | High availability | | Public subnets | 2 (`10.0.1.0/24`, `10.0.2.0/24`) | ALB, NAT Gateway | | Private subnets (compute) | 2 (`10.0.10.0/24`, `10.0.20.0/24`) | ECS tasks | | Private subnets (data) | 2 (`10.0.100.0/24`, `10.0.200.0/24`) | RDS, MSK, EKS | | NAT Gateway | 1 (or 2 for HA) | Private subnet internet access | | Internet Gateway | 1 | Public subnet internet access | ### Create VPC with AWS CLI Create the VPC, enable DNS hostnames, and attach an Internet Gateway. ### Create subnets Create public and private subnets in two Availability Zones using the CIDRs in the VPC configuration table. ### Create NAT Gateway Create an Elastic IP and NAT Gateway in a public subnet. ### Create route tables Create public and private route tables and associate subnets (public: default route to Internet Gateway; private: default route to NAT Gateway). ### Create security groups Create security groups for ALB, ECS, RDS, MSK, and EKS. Use the rules in the summary table below. ### Security group rules summary | Security Group | Inbound | Source | Port(s) | Purpose | | ------------------ | ------- | ----------- | ---------------- | ------------------------ | | `flexprice-alb-sg` | HTTPS | `0.0.0.0/0` | 443 | Public API access | | `flexprice-alb-sg` | HTTP | `0.0.0.0/0` | 80 | Redirect to HTTPS | | `flexprice-ecs-sg` | TCP | `alb-sg` | 8080 | ALB to API | | `flexprice-ecs-sg` | TCP | `ecs-sg` | All | Inter-task communication | | `flexprice-rds-sg` | TCP | `ecs-sg` | 5432 | PostgreSQL access | | `flexprice-msk-sg` | TCP | `ecs-sg` | 9092, 9094, 9096 | Kafka access | | `flexprice-eks-sg` | TCP | `ecs-sg` | 9000, 8123 | ClickHouse access | For production, consider restricting the ALB security group to only Cloudflare IP ranges if you're using Cloudflare for DNS and WAF. *** ## Step 2: IAM roles and policies Create IAM roles for ECS task execution and task runtime permissions. ### ECS Task Execution Role This role allows ECS to pull container images and write logs. Create the role and attach the managed policy `AmazonECSTaskExecutionRolePolicy` plus an inline policy for Secrets Manager access. ### ECS Task Role This role grants permissions for the Flexprice application at runtime (S3, CloudWatch Logs, Secrets Manager). Create the task role and attach the inline policy. *** ## Step 3: Secrets Manager Store sensitive configuration in AWS Secrets Manager. ### Create secrets Create secrets for PostgreSQL, ClickHouse, Kafka (SASL), auth, and Temporal Cloud. Store postgres (host, username, password, database), clickhouse (username, password), kafka (username, password), auth (64-char hex secret), and temporal (API key, key name, namespace) as needed. Replace placeholder values with strong, unique credentials. Use a password generator for production secrets. *** ## Step 4: Aurora PostgreSQL Create an Aurora PostgreSQL cluster for Flexprice's primary database. Aurora provides higher availability and performance compared to standard RDS. Create a DB subnet group, Aurora cluster (with Secrets Manager managed credentials), writer instance (db.r8g.xlarge), and reader instance in the other AZ. Retrieve the cluster writer and reader endpoints for application configuration. For development, use standard RDS PostgreSQL (e.g. db.t3.small) via AWS Console, CLI, or IaC. ### Aurora configuration summary | Setting | Development | Production | | ---------------- | --------------- | ----------------------------------- | | Engine | PostgreSQL 15.4 | Aurora PostgreSQL 17.4 | | Instance class | `db.t3.small` | `db.r8g.xlarge` (4 vCPU, 32 GB) | | Instances | 1 (Single-AZ) | 2 (Writer + Reader, Multi-AZ) | | Storage | 100 GB gp3 | Aurora I/O-Optimized (auto-scaling) | | Multi-AZ | No | Yes (2 zones) | | Encryption | Enabled | Enabled | | Backup retention | 7 days | 7 days | | Monthly cost | \~\$30 | \~\$650 | ### Update Secrets Manager with Aurora endpoints Update the postgres secret in Secrets Manager with the Aurora writer and reader endpoints and the managed master password ARN. Aurora with Secrets Manager managed credentials automatically rotates the master password. Use the `MasterUserSecret` ARN to retrieve the current password. ### Run database migrations You can run migrations using a one-off ECS task or from a bastion host. Create a migration task definition and run it via ECS (or run `flexprice migrate up` from a host with DB access) using the configuration described above. *** ## Step 5: Amazon MSK (Kafka) Create an Amazon MSK cluster for event streaming. ### Create MSK configuration Create an MSK configuration (server properties) and register it. ### Create MSK cluster Create the MSK cluster with **2 brokers** (1 per AZ), **kafka.m5.large** (4 vCPU, 8 GB) instance type, and **1024 GB (1 TB) storage per broker**. Enable SASL/SCRAM, TLS, encryption at rest, and enhanced monitoring. ### Create SASL/SCRAM secret for MSK Create a secret in Secrets Manager with the prefix `AmazonMSK_` and associate it with the MSK cluster. ### Get MSK bootstrap brokers Retrieve the SASL/SCRAM bootstrap broker string from the MSK cluster (AWS Console or CLI) for application configuration. ### Create Kafka topics Use a bastion host or an EC2 instance with Kafka CLI tools to create the `events` and `events-dlq` topics (e.g. 6 partitions, replication factor 2). Use SASL\_SSL and SCRAM-SHA-512 in client configuration. ### MSK configuration summary | Setting | Development | Production | | ------------------ | ---------------- | ------------------------------------- | | Kafka version | 3.5.1 | 3.8.1 | | Broker type | `kafka.t3.small` | `kafka.m5.large` (4 vCPU, 8 GB) | | Number of brokers | 2 | 2 (1 per AZ) | | Storage per broker | 100 GB | 1024 GB (1 TB) | | Authentication | SASL/SCRAM | SASL/SCRAM + IAM | | Encryption | TLS in transit | TLS in transit + at rest | | Monitoring | Basic | Enhanced partition-level + Prometheus | | Monthly cost | \~\$90 | \~\$350 | For development, use `kafka.t3.small` with 100 GB storage. For production (100M+ events/month), use **2 brokers**, **kafka.m5.large**, and **1 TB storage per broker**. *** ## Step 6: EKS with ClickHouse Create an EKS cluster and deploy ClickHouse for analytics storage. For production (100M+ events/month), use **m5.8xlarge** nodes for the ClickHouse node group. ### Create EKS cluster with eksctl Create an EKS cluster with a managed node group (m5.8xlarge for production) via eksctl or IaC. Use private subnets and attach the EKS security group. ### Create gp3 StorageClass Create a gp3 StorageClass (EBS CSI driver, encrypted, Retain, WaitForFirstConsumer) via kubectl or IaC. ### Create ClickHouse namespace and secrets Create the `clickhouse` namespace and a Kubernetes secret with credentials from Secrets Manager via kubectl or IaC. ### Deploy ClickHouse with Helm Add the Altinity ClickHouse Helm repo and install the ClickHouse Operator in the `clickhouse` namespace via Helm. ### Create ClickHouse cluster Deploy a ClickHouseInstallation (Altinity operator) with the credentials secret, gp3 storage, and appropriate resources via kubectl or Helm. ### Create ClickHouse service for ECS access Create a ClusterIP Service for ClickHouse (ports 9000, 8123) targeting the ClickHouse installation via kubectl or IaC. ### Get ClickHouse endpoint For ECS tasks to access ClickHouse, you have several options: 1. **Internal NLB** (recommended): Create an internal Network Load Balancer pointing to the ClickHouse service 2. **VPC peering/Transit Gateway**: If ECS and EKS are in separate VPCs 3. **AWS PrivateLink**: For cross-account access Create the internal NLB (type LoadBalancer with internal annotation) and use its DNS name as the ClickHouse endpoint (port 9000) for ECS configuration. ### Initialize ClickHouse database Connect to ClickHouse (e.g. via port-forward or the NLB) and create the `flexprice` database using clickhouse-client. *** ## Step 7: ElastiCache Redis Create an ElastiCache Redis cluster for caching and session management. ### Create Redis subnet group Create a cache subnet group in the data subnets. ### Create Redis security group Create a security group for Redis allowing TCP 6379 from the ECS security group. ### Create Redis replication group (cluster mode) Create a Redis replication group with cache.r6g.large, cluster mode, multi-node (e.g. multiple node groups for \~\$600/month), TLS and at-rest encryption, and multi-AZ. Create a single-node Redis cluster (cache.t3.micro). ### Redis configuration summary | Setting | Development | Production (multi-node cluster) | | ------------ | ---------------- | --------------------------------- | | Node type | `cache.t3.micro` | `cache.r6g.large` (2 vCPU, 13 GB) | | Cluster mode | Disabled | Enabled | | Replicas | 0 | 1 per shard | | Multi-AZ | No | Yes | | Encryption | Optional | TLS in transit + at rest | | Monthly cost | \~\$15 | \~\$600 | *** ## Step 8: DynamoDB Create a DynamoDB table for durable event storage alongside ClickHouse. ### Create events table Create a DynamoDB table named `events` with partition key `pk` (String) and sort key `sk` (String), on-demand billing. ### Enable Point-in-Time Recovery Enable point-in-time recovery (continuous backups) on the events table. ### DynamoDB configuration summary | Setting | Value | Notes | | ------------- | ------------- | ---------------------------- | | Billing mode | On-demand | Pay per request, auto-scales | | Partition key | `pk` (String) | Tenant/customer ID | | Sort key | `sk` (String) | Event timestamp | | PITR | Enabled | Point-in-time recovery | | Encryption | AWS managed | Default encryption | | Monthly cost | \~\$50 | For \~100M events/month | DynamoDB is used alongside ClickHouse for durable event storage. Events are written to both DynamoDB (for durability) and ClickHouse (for analytics). *** ## Step 9: S3 and CloudWatch ### Create S3 bucket for invoices Create an S3 bucket for invoice PDFs with versioning, AES256 encryption, block public access, and optional lifecycle rules (e.g. transition to STANDARD\_IA after 90 days). ### Create CloudWatch log groups Create log groups for ECS services (api, worker, temporal-worker, migration) with a retention policy (e.g. 30 days). ### Create CloudWatch alarms Create alarms for ECS API CPU, RDS CPU, and RDS connections (e.g. threshold 80%, 2 evaluation periods) and associate with an SNS topic for alerts. *** ## Step 10: ECR and container images ### Create ECR repositories Create ECR repositories for api, worker, and temporal-worker with scan-on-push and AES256 encryption. ### Build and push images Build Flexprice container images (api, worker, temporal-worker), authenticate to ECR, tag and push to your ECR repositories. *** ## Step 11: ECS cluster and services ### Create ECS cluster For production (100M+ events/month), create an ECS cluster with EC2 capacity: launch template with **m6g.xlarge** (ARM64/Graviton), Auto Scaling Group with **10 nodes** (min/max as needed), capacity provider with managed scaling, and associate with the cluster. For development, create an ECS cluster with Fargate and FARGATE\_SPOT capacity providers. ### Create API task definition Register an ECS task definition for the API service: production uses EC2/ARM64 (768 CPU, 1536 memory) with bridge network; development uses Fargate (1024 CPU, 2048 memory). Include environment variables and secrets from Secrets Manager (auth, postgres, clickhouse, kafka, temporal). Set FLEXPRICE\_DEPLOYMENT\_MODE=api, health check on :8080/health, and CloudWatch log group. See Step 12 for the full environment variable reference. ### Create Worker task definition Register an ECS task definition for the Consumer (worker) service: FLEXPRICE\_DEPLOYMENT\_MODE=consumer, postgres/clickhouse/kafka secrets from Secrets Manager. For production use **30 tasks** (100M events/month). See Step 12 for environment variables. ### Create Temporal Worker task definition Register an ECS task definition for the Temporal Worker: FLEXPRICE\_DEPLOYMENT\_MODE=temporal\_worker, postgres/clickhouse/kafka/temporal secrets. See Step 12 for environment variables. ### Create Application Load Balancer Create an internet-facing Application Load Balancer in the public subnets, a target group (HTTP 8080, health check /health), an HTTPS listener with an ACM certificate, and an HTTP listener that redirects to HTTPS. ### Create ECS services Create ECS services for API (desired count **6** for production), Worker/Consumer (desired count **30** for production), and Temporal Worker (e.g. 3 tasks). Attach the API service to the ALB target group. Use private subnets and the ECS security group. ### Configure Auto Scaling Register scalable targets and target-tracking scaling policies for the API (and optionally Worker) services (e.g. min/max desired count, CPU target 70%). *** ## Step 12: Environment variables reference Below is a complete reference of environment variables for each service. Variables marked with (secret) should be stored in AWS Secrets Manager. ### API service | Variable | Value | Source | | -------------------------------- | ------------------------- | ----------- | | `FLEXPRICE_DEPLOYMENT_MODE` | `api` | Environment | | `FLEXPRICE_SERVER_ADDRESS` | `:8080` | Environment | | `FLEXPRICE_AUTH_SECRET` | 64-char hex | Secret | | `FLEXPRICE_POSTGRES_HOST` | RDS endpoint | Secret | | `FLEXPRICE_POSTGRES_PORT` | `5432` | Environment | | `FLEXPRICE_POSTGRES_USER` | `flexprice` | Secret | | `FLEXPRICE_POSTGRES_PASSWORD` | DB password | Secret | | `FLEXPRICE_POSTGRES_DBNAME` | `flexprice` | Environment | | `FLEXPRICE_POSTGRES_SSLMODE` | `require` | Environment | | `FLEXPRICE_CLICKHOUSE_ADDRESS` | ClickHouse NLB endpoint | Environment | | `FLEXPRICE_CLICKHOUSE_USERNAME` | `flexprice` | Secret | | `FLEXPRICE_CLICKHOUSE_PASSWORD` | ClickHouse password | Secret | | `FLEXPRICE_CLICKHOUSE_DATABASE` | `flexprice` | Environment | | `FLEXPRICE_CLICKHOUSE_TLS` | `false` | Environment | | `FLEXPRICE_KAFKA_BROKERS` | MSK bootstrap brokers | Environment | | `FLEXPRICE_KAFKA_USE_SASL` | `true` | Environment | | `FLEXPRICE_KAFKA_SASL_MECHANISM` | `SCRAM-SHA-512` | Environment | | `FLEXPRICE_KAFKA_SASL_USER` | `flexprice` | Secret | | `FLEXPRICE_KAFKA_SASL_PASSWORD` | Kafka password | Secret | | `FLEXPRICE_KAFKA_TOPIC` | `events` | Environment | | `FLEXPRICE_KAFKA_CONSUMER_GROUP` | `flexprice-consumer-prod` | Environment | | `FLEXPRICE_TEMPORAL_ADDRESS` | Temporal Cloud endpoint | Environment | | `FLEXPRICE_TEMPORAL_TLS` | `true` | Environment | | `FLEXPRICE_TEMPORAL_NAMESPACE` | Your namespace | Environment | | `FLEXPRICE_TEMPORAL_TASK_QUEUE` | `billing-task-queue` | Environment | | `FLEXPRICE_TEMPORAL_API_KEY` | Temporal API key | Secret | | `FLEXPRICE_LOGGING_LEVEL` | `info` | Environment | ### Worker and Temporal Worker services Worker and Temporal Worker use the same variables as API, with `FLEXPRICE_DEPLOYMENT_MODE` set to `consumer` or `temporal_worker` respectively; omit `FLEXPRICE_SERVER_ADDRESS` for both. ### Additional environment variables (Production) These variables are used in production deployments: | Variable | Description | Example | | ------------------------------------- | -------------------------- | -------------------------------------- | | `FLEXPRICE_DYNAMODB_IN_USE` | Enable DynamoDB for events | `true` | | `FLEXPRICE_DYNAMODB_REGION` | AWS region for DynamoDB | `us-west-2` | | `FLEXPRICE_DYNAMODB_EVENT_TABLE_NAME` | DynamoDB table name | `events` | | `FLEXPRICE_REDIS_HOST` | ElastiCache Redis endpoint | `clustercfg.xxx.cache.amazonaws.com` | | `FLEXPRICE_REDIS_PORT` | Redis port | `6379` | | `FLEXPRICE_REDIS_CLUSTER_MODE` | Enable cluster mode | `true` | | `FLEXPRICE_REDIS_USE_TLS` | Enable TLS | `true` | | `FLEXPRICE_REDIS_KEY_PREFIX` | Key prefix | `flexprice:prod` | | `FLEXPRICE_EVENT_PUBLISH_DESTINATION` | Where to publish events | `all` (Kafka + DynamoDB) | | `FLEXPRICE_LOGGING_FORMAT` | Log format | `json` | | `FLEXPRICE_POSTGRES_READER_HOST` | Aurora reader endpoint | `xxx.cluster-ro-xxx.rds.amazonaws.com` | *** ## Step 13: Temporal Cloud configuration Temporal Cloud is the recommended workflow orchestration service for production deployments. ### Sign up for Temporal Cloud 1. Go to [temporal.io/cloud](https://temporal.io/cloud) 2. Create an account and organization 3. Create a namespace (e.g., `flexprice-prod-usa`) ### Create service account and API key 1. In Temporal Cloud console, go to **Settings** > **API Keys** 2. Create a new API key with appropriate permissions 3. Note the API key and key name ### Store Temporal credentials ```bash theme={null} aws secretsmanager create-secret \ --name flexprice/${ENV}/temporal \ --description "Flexprice Temporal Cloud credentials" \ --secret-string '{ "address": "us-west-2.aws.api.temporal.io:7233", "namespace": "your-namespace.your-account-id", "api_key": "YOUR_TEMPORAL_API_KEY", "api_key_name": "your-service-account-name" }' ``` ### Temporal environment variables | Variable | Value | Description | | --------------------------------- | ------------------------------------ | ----------------------- | | `FLEXPRICE_TEMPORAL_ADDRESS` | `us-west-2.aws.api.temporal.io:7233` | Temporal Cloud endpoint | | `FLEXPRICE_TEMPORAL_NAMESPACE` | `your-namespace.account-id` | Your namespace | | `FLEXPRICE_TEMPORAL_TLS` | `true` | TLS is required | | `FLEXPRICE_TEMPORAL_TASK_QUEUE` | `billing-task-queue` | Task queue name | | `FLEXPRICE_TEMPORAL_API_KEY` | (from Secrets Manager) | API key | | `FLEXPRICE_TEMPORAL_API_KEY_NAME` | Service account name | Key identifier | Temporal Cloud provides managed infrastructure, automatic upgrades, and 99.99% SLA. For self-hosted Temporal, refer to the [Temporal documentation](https://docs.temporal.io/self-hosted-guide). *** ## Step 14: Third-party integrations (Optional) Configure optional third-party services for enhanced functionality. ### Supabase (Authentication) If using Supabase for authentication: ```bash theme={null} aws secretsmanager create-secret \ --name flexprice/${ENV}/supabase \ --secret-string '{ "base_url": "https://your-project.supabase.co", "service_key": "YOUR_SUPABASE_SERVICE_KEY" }' ``` | Variable | Value | | ------------------------------------- | -------------------- | | `FLEXPRICE_AUTH_PROVIDER` | `supabase` | | `FLEXPRICE_AUTH_SUPABASE_BASE_URL` | Supabase project URL | | `FLEXPRICE_AUTH_SUPABASE_SERVICE_KEY` | Service role key | ### Svix (Webhooks) For webhook delivery via Svix: ```bash theme={null} aws secretsmanager create-secret \ --name flexprice/${ENV}/svix \ --secret-string '{ "auth_token": "YOUR_SVIX_AUTH_TOKEN", "base_url": "https://api.us.svix.com" }' ``` | Variable | Value | | ------------------------------------------ | ------------------------- | | `FLEXPRICE_WEBHOOK_SVIX_CONFIG_ENABLED` | `true` | | `FLEXPRICE_WEBHOOK_SVIX_CONFIG_AUTH_TOKEN` | Svix auth token | | `FLEXPRICE_WEBHOOK_SVIX_CONFIG_BASE_URL` | `https://api.us.svix.com` | ### Sentry (Error Tracking) For error tracking with Sentry: | Variable | Value | | ------------------------------ | ------------------- | | `FLEXPRICE_SENTRY_ENABLED` | `true` | | `FLEXPRICE_SENTRY_DSN` | Your Sentry DSN | | `FLEXPRICE_SENTRY_ENVIRONMENT` | `production` | | `FLEXPRICE_SENTRY_SAMPLE_RATE` | `1` (100% sampling) | ### Grafana Cloud (Observability) For profiling with Pyroscope on Grafana Cloud: | Variable | Value | | ----------------------------------------- | --------------------------------------- | | `FLEXPRICE_PYROSCOPE_ENABLED` | `true` | | `FLEXPRICE_PYROSCOPE_SERVER_ADDRESS` | `https://profiles-prod-xxx.grafana.net` | | `FLEXPRICE_PYROSCOPE_APPLICATION_NAME` | `flexprice-prod-api` | | `FLEXPRICE_PYROSCOPE_BASIC_AUTH_USER` | Grafana user ID | | `FLEXPRICE_PYROSCOPE_BASIC_AUTH_PASSWORD` | Grafana API key | ### FluentD (Log Aggregation) For centralized logging with FluentD: | Variable | Value | | ----------------------------------- | ------------------ | | `FLEXPRICE_LOGGING_FLUENTD_ENABLED` | `true` | | `FLEXPRICE_LOGGING_FLUENTD_HOST` | FluentD service IP | | `FLEXPRICE_LOGGING_FLUENTD_PORT` | `30242` | | `FLEXPRICE_LOGGING_FORMAT` | `json` | ### Resend (Email) For transactional emails via Resend: | Variable | Value | | -------------------------------- | ------------------- | | `FLEXPRICE_EMAIL_ENABLED` | `true` | | `FLEXPRICE_EMAIL_RESEND_API_KEY` | Your Resend API key | | `FLEXPRICE_EMAIL_FROM_ADDRESS` | Sender email | | `FLEXPRICE_EMAIL_REPLY_TO` | Reply-to email | ### Third-party cost summary Breakdown below; production total is in the Cost estimation table above. | Service | Purpose | Monthly Cost | | -------------- | ---------------------- | --------------- | | Temporal Cloud | Workflow orchestration | \~\$200 | | Supabase | Authentication | \~\$25 | | Svix | Webhooks | \~\$50 | | Grafana Cloud | Observability | \~\$50 | | Resend | Email | \~\$20 | | Sentry | Error tracking | \$0-29 | | **Total** | | **\~\$345-375** | *** ## Deployment checklist Use this checklist to verify your deployment: * [ ] VPC created with correct CIDR * [ ] 2 public subnets created * [ ] 4 private subnets created (2 compute, 2 data) * [ ] Internet Gateway attached * [ ] NAT Gateway(s) created and running * [ ] Route tables configured correctly * [ ] Security groups created with correct rules * [ ] ECS Task Execution Role created * [ ] ECS Task Role created * [ ] Policies attached correctly (S3, Secrets Manager, CloudWatch, DynamoDB) * [ ] PostgreSQL/Aurora credentials stored * [ ] ClickHouse credentials stored * [ ] Kafka SASL credentials stored * [ ] Auth secret stored * [ ] Temporal Cloud credentials stored * [ ] Third-party credentials stored (Supabase, Svix, etc.) * [ ] DB subnet group created * [ ] Aurora cluster created and available * [ ] Writer and Reader instances running * [ ] Security group allows ECS access * [ ] Secrets Manager updated with endpoints * [ ] Database migrations completed * [ ] MSK cluster created and active * [ ] SASL/SCRAM secret associated * [ ] Topics created (events, events\_lazy, events-dlq) * [ ] Security group allows ECS access * [ ] Prometheus exporters enabled * [ ] EKS cluster created * [ ] Node group running * [ ] gp3 StorageClass created * [ ] ClickHouse operator installed * [ ] ClickHouse cluster deployed * [ ] NLB created for ClickHouse access * [ ] Database initialized * [ ] Redis subnet group created * [ ] Redis replication group created * [ ] Cluster mode enabled (production) * [ ] TLS encryption enabled * [ ] Security group allows ECS access * [ ] Events table created * [ ] Point-in-time recovery enabled * [ ] IAM policy allows ECS access * [ ] S3 bucket created with encryption * [ ] CloudWatch log groups created * [ ] CloudWatch alarms configured * [ ] ECR repositories created * [ ] Container images built and pushed * [ ] ECS cluster created * [ ] Task definitions registered * [ ] ALB created with HTTPS listener * [ ] Target group configured * [ ] Services created and healthy * [ ] Auto Scaling configured * [ ] API health check passing * [ ] Worker consuming from Kafka * [ ] Temporal workflows executing * [ ] Logs appearing in CloudWatch *** ## Troubleshooting ### API unreachable 1. **Check ALB health checks**: ```bash theme={null} aws elbv2 describe-target-health --target-group-arn $TG_ARN ``` 2. **Check ECS task status**: ```bash theme={null} aws ecs describe-services \ --cluster flexprice-${ENV} \ --services flexprice-api-${ENV} ``` 3. **Check ECS task logs**: ```bash theme={null} aws logs tail /ecs/flexprice-api-${ENV} --follow ``` 4. **Verify security groups**: * ALB SG allows inbound 443 from internet * ECS SG allows inbound 8080 from ALB SG * ECS SG allows outbound to RDS, MSK, ClickHouse ### Worker not consuming 1. **Check Kafka connectivity**: ```bash theme={null} # From a bastion or EC2 instance with Kafka tools kafka-consumer-groups.sh \ --bootstrap-server $MSK_BOOTSTRAP \ --command-config client.properties \ --group flexprice-consumer-${ENV} \ --describe ``` 2. **Check consumer lag in MSK CloudWatch metrics** 3. **Verify SASL credentials**: * Ensure `AmazonMSK_` prefixed secret is associated with cluster * Verify username/password match in Secrets Manager 4. **Check security group**: * MSK SG allows inbound 9094/9096 from ECS SG ### Temporal workflows failing 1. **Check Temporal Worker logs**: ```bash theme={null} aws logs tail /ecs/flexprice-temporal-worker-${ENV} --follow ``` 2. **Verify Temporal Cloud connection**: * Correct `FLEXPRICE_TEMPORAL_ADDRESS` * Valid API key and namespace * TLS enabled 3. **Check Temporal Cloud UI** for workflow history and errors ### ClickHouse connection errors 1. **Verify ClickHouse pods are running**: ```bash theme={null} kubectl get pods -n clickhouse ``` 2. **Check ClickHouse logs**: ```bash theme={null} kubectl logs -n clickhouse -l clickhouse.altinity.com/chi=flexprice ``` 3. **Verify NLB is healthy**: ```bash theme={null} kubectl get svc clickhouse-nlb -n clickhouse ``` 4. **Test connectivity from ECS**: * Ensure EKS SG allows inbound 9000 from ECS SG * Verify NLB DNS resolves correctly ### RDS connection issues 1. **Verify RDS is available**: ```bash theme={null} aws rds describe-db-instances \ --db-instance-identifier flexprice-${ENV} \ --query 'DBInstances[0].DBInstanceStatus' ``` 2. **Check security group**: * RDS SG allows inbound 5432 from ECS SG 3. **Verify credentials**: * Check Secrets Manager values match RDS configuration 4. **Test from bastion**: ```bash theme={null} psql -h $RDS_ENDPOINT -U flexprice -d flexprice ``` *** ## Scaling guidelines Scale when metrics exceed the thresholds below. | Component | Metric | Threshold | Action | | ---------- | -------------------- | ----------------- | ------------------------------------ | | ECS | CPU utilization | > 70% sustained | Scale out | | ECS | Memory utilization | > 80% sustained | Scale out or increase task memory | | ECS | API latency (p99) | > 500ms | Scale out API tasks | | ECS | Kafka consumer lag | Growing | Scale out Worker tasks | | RDS | CPU utilization | > 80% sustained | Upgrade instance class | | RDS | Database connections | > 80% of max | Upgrade instance or add read replica | | RDS | Read IOPS | Hitting limits | Upgrade to gp3 with higher IOPS | | RDS | Storage | > 80% used | Increase allocated storage | | MSK | Broker CPU | > 60% sustained | Add brokers | | MSK | Consumer lag | Growing over time | Add partitions and consumers | | MSK | Storage | > 80% used | Increase broker storage | | ClickHouse | Query latency | Degrading | Add replicas or upgrade nodes | | ClickHouse | Disk usage | > 80% | Expand PVCs or add shards | | ClickHouse | Memory pressure | OOM events | Increase node memory | *** ## Cost optimization ### Reserved instances * **RDS**: Purchase Reserved Instances for 1-3 year commitment (up to 72% savings) * **MSK**: Not available; consider Kafka on EC2 with Reserved Instances for significant savings ### Fargate Spot Use Fargate Spot for non-critical workloads: ```bash theme={null} # Update service to use Fargate Spot aws ecs update-service \ --cluster flexprice-${ENV} \ --service flexprice-worker-${ENV} \ --capacity-provider-strategy capacityProvider=FARGATE_SPOT,weight=2 capacityProvider=FARGATE,weight=1 ``` ### S3 lifecycle policies Already configured to transition to IA after 90 days. Consider: * Glacier for archives > 1 year * Intelligent-Tiering for unpredictable access patterns ### CloudWatch log retention Set appropriate retention periods: * Production: 30-90 days * Development: 7-14 days * Archive to S3 for long-term storage *** ## Additional resources Complete list of Flexprice environment variables Understand Flexprice's internal architecture Set up monitoring and observability Common issues and solutions ## Need help? If you encounter issues during deployment: * Check our [GitHub Issues](https://github.com/flexprice/flexprice/issues) for similar problems * Join our [Slack community](https://join.slack.com/t/flexpricecommunity/shared_invite/zt-39uat51l0-n8JmSikHZP~bHJNXladeaQ) for real-time support * Contact us at [support@flexprice.io](mailto:support@flexprice.io) # Self-hosting the Frontend Source: https://docs.flexprice.io/docs/getting-started/self-hosting-frontend Run the Flexprice dashboard on your own infrastructure and connect it to a self-hosted backend The [backend self-hosting guide](/docs/getting-started/self-hosting-guide) gets the Flexprice API and its infrastructure running. This guide covers the other half: running the Flexprice dashboard (`flexprice-front`) yourself and pointing it at that backend. Complete the [backend self-hosting guide](/docs/getting-started/self-hosting-guide) first. The frontend is a static single-page app that talks to the API over HTTP; it has no database or infrastructure of its own. ## Self-hosting options Run the dashboard in a container alongside your self-hosted backend. Best for a single server. Build static files with `npm run build` and serve them with nginx or any static host. Deploy the same repo to Vercel using the bundled `vercel.json`. *** ## Self-hosted environment configuration Before you build or deploy anything, set these. They're the three variables that actually change how the app behaves in a self-hosted setup, and getting them wrong is the single most common way this deployment breaks: | Variable | Self-hosted value | Why | | -------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `VITE_APP_ENV` | `self-hosted` | Switches the dashboard's auth flow to call your backend's own `/auth/login` and `/auth/signup` endpoints directly, and stores the session token in the browser instead of using Supabase. Without this, the app expects a Supabase project to exist. | | `VITE_AUTH_PROVIDER` | `flexprice` | Confirms the dashboard is using Flexprice's own auth rather than Supabase, and hides Supabase-only UI (Google sign-in, cloud announcement banners) that don't apply to a self-hosted instance. | | `VITE_API_URL` | `http://:8080/v1` | Points the dashboard at your self-hosted API. See [Connecting to the backend](#connecting-to-the-backend) for what "reachable" means here. | The `.env.example` file defaults `VITE_AUTH_PROVIDER` to `supabase` and leaves `VITE_APP_ENV` unset (which falls back to `local`). If you skip setting these two values, login and signup will silently try to reach Supabase instead of your backend. With `VITE_APP_ENV=self-hosted` set, you do **not** need to fill in `VITE_SUPABASE_URL` or `VITE_SUPABASE_ANON_KEY` — leave them blank. Vite bakes every `VITE_*` variable into the static bundle at **build time**, not at container start. Whenever you change `.env`, you must rebuild (`docker compose up -d --build`, or `npm run build` again) before the change takes effect. This trips people up more than anything else in this guide, see [Troubleshooting](#troubleshooting). ### Full environment variable reference | Variable | Self-hosted value | Notes | | -------------------- | ------------------------------------------------------- | ----------------------------------------------------------------------------------------- | | `VITE_APP_ENV` | `self-hosted` | See above. Also accepts `local`, `development`, `production` for other setups. | | `VITE_API_URL` | Your backend's API URL, e.g. `http://localhost:8080/v1` | Must be reachable from the browser, not just the server. | | `VITE_AUTH_ENABLED` | `true` | Set this regardless; the dashboard always requires login. Kept for forward compatibility. | | `VITE_AUTH_PROVIDER` | `flexprice` | Use Flexprice's built-in auth instead of Supabase. | | Variable | Self-hosted value | Notes | | ------------------------ | ----------------- | --------------------------------------------- | | `VITE_SUPABASE_URL` | Leave blank | Only used when `VITE_AUTH_PROVIDER=supabase`. | | `VITE_SUPABASE_ANON_KEY` | Leave blank | Same as above. | | Variable | Recommended for self-hosted | Notes | | ---------------------- | --------------------------------------- | --------------------------------------------------------------- | | `VITE_SENTRY_ENABLED` | `false` unless you run your own Sentry | Set `VITE_SENTRY_DSN` if enabled. | | `VITE_POSTHOG_ENABLED` | `false` unless you run your own PostHog | Set `VITE_POSTHOG_KEY` / `VITE_POSTHOG_HOST` if enabled. | | `VITE_REO_ENABLED` | `false` | Cloud session-replay integration; not relevant for self-hosted. | | Variable | Self-hosted value | Notes | | ----------------------- | ----------------- | -------------------------------------------------------------------------------------- | | `VITE_PADDLE_ENABLED` | `false` | Paddle checkout is used by Flexprice Cloud billing, not applicable when you self-host. | | `VITE_INTERCOM_ENABLED` | `false` | Flexprice Cloud support widget. | | Variable | Self-hosted value | Notes | | ----------------------- | ---------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `VITE_WEBHOOK_PROVIDER` | `flexprice` | Uses the custom Flexprice webhook portal instead of hosted Svix. Set to `svix` only if you're self-hosting Svix yourself. | | `VITE_SVIX_URL` | Public origin of your self-hosted Svix API, or leave blank | Only needed if you run Svix yourself and set `VITE_WEBHOOK_PROVIDER=svix`. | | Variable | Self-hosted value | Notes | | ---------------------------------------------------- | ----------------- | ----------------------------------------------------------------------------------------------- | | `VITE_DASHBOARD_URL_INDIA` / `VITE_DASHBOARD_URL_US` | Leave blank | Used to route between Flexprice Cloud regions; not applicable to a single self-hosted instance. | | `VITE_DATA_REGION_SELECTION_ENABLED` | `false` | Same as above. | | `VITE_RESTRICTED_ENVS` | Leave blank | Used by Flexprice Cloud to suspend tenant environments. | | `VITE_TENANT_FEATURE_ALLOWLIST` | Leave blank | Gates cloud-only UI for specific tenants; irrelevant when you're the only tenant. | | Variable | Notes | | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `VITE_GOOGLE_SHEETS_WEB_APP_URL` | Only needed if you use the Google Sheets export integration. | | `VITE_FONT_CONFIG` | Optional JSON override for dashboard typography, e.g. `{"primary":"Inter","fallback":"ui-sans-serif, system-ui, sans-serif"}`. Omit to use the default font. | *** ## First login: create your account A self-hosted instance starts with no dashboard users. There's no default username or password: every account, including your first one, is created through Sign Up against your own backend: Once your container or dev server is running, visit it in a browser (e.g. `http://localhost:3000`). On the auth screen, switch to the **Sign Up** tab and enter an email and password (6+ characters). In self-hosted mode, sign-up skips email verification entirely: the app posts straight to your backend's `/auth/signup` endpoint and logs you in right away using the returned session token. There's no confirmation email to wait for. If Sign Up fails with a network or CORS error instead of a validation error, that's almost always `VITE_API_URL` or backend CORS, not the auth flow itself. See [Troubleshooting](#troubleshooting). *** ## Docker Compose ### Prerequisites [Git](https://git-scm.com/) [Node.js](https://nodejs.org/) 20+ and npm (only needed for a manual build; skip if you're using Docker exclusively) [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) A running Flexprice backend, reachable over HTTP from wherever the frontend runs. See the [backend self-hosting guide](/docs/getting-started/self-hosting-guide). ### Quick start ```bash theme={null} # Clone the frontend repository git clone https://github.com/flexprice/flexprice-front cd flexprice-front # Copy the environment template cp .env.example .env ``` Edit `.env` with at least the three variables from [self-hosted environment configuration](#self-hosted-environment-configuration) above, then build and start the container: ```bash theme={null} docker compose up -d --build ``` Use `--build` here, and every time you change `.env` afterward. `docker compose up -d` alone reuses a cached image if one already exists, which silently keeps your old environment variables since Vite bakes them in at build time. The `docker-compose.yml` in the repo builds the image from the included `Dockerfile` (a multi-stage Node 20 build) and exposes the dashboard on port 3000 with a built-in healthcheck. ```bash theme={null} # View logs docker compose logs -f app # Stop docker compose down ``` Visit `http://localhost:3000` once the container reports healthy, then follow [First login](#first-login) to create your account. *** ## Manual build and serve If you'd rather not use Docker, build static files and serve them yourself. ```bash theme={null} git clone https://github.com/flexprice/flexprice-front cd flexprice-front npm install cp .env.example .env # Edit .env: VITE_APP_ENV=self-hosted, VITE_AUTH_PROVIDER=flexprice, # VITE_API_URL=http://:8080/v1 npm run build ``` This produces a static `dist/` folder. Serve it with any of the following: Copy the repo's `nginx.conf` (SPA fallback already configured) and point it at `dist/`: ```bash theme={null} cp nginx.conf /etc/nginx/conf.d/flexprice.conf nginx -s reload ``` ```bash theme={null} npm run start # Serves on 0.0.0.0:3000 ``` ```bash theme={null} npx serve -s dist ``` Whatever you use to serve the built files, it must fall back to `index.html` for unknown paths (a single-page app client-side router). The bundled `nginx.conf` handles this with `try_files $uri $uri/ /index.html;`. If you configure your own web server, replicate this rule or client-side routes like `/customers/123` will 404 on a hard refresh. *** ## Deploying on Vercel The repo includes a `vercel.json` with the SPA rewrite already configured (`/(.*) → /`), so you can deploy directly: 1. Import the `flexprice-front` repository into Vercel. 2. Set the environment variables from the [reference above](#self-hosted-environment-configuration) in the Vercel project settings (`VITE_APP_ENV=self-hosted`, `VITE_AUTH_PROVIDER=flexprice`, `VITE_API_URL`, and any optional ones you need). 3. Deploy. Vercel runs `npm run build` automatically and serves the `dist/` output. Your backend must be reachable from the public internet (or accessible to wherever your Vercel deployment resolves `VITE_API_URL`) since Vercel doesn't proxy to a private network by default. *** ## Connecting to the backend * `VITE_API_URL` is baked into the JavaScript bundle and fetched by the **end user's browser**, not by the Docker container or server that built it. Use an address the browser can actually reach: the host machine's real IP or domain, not a Docker-internal hostname. `http://localhost:8080/v1` only works if the person opening the dashboard is on the same machine as the backend. * CORS: the Flexprice API needs to allow requests from the origin the dashboard is served on. If you're serving the frontend from a different domain or port than the API expects, configure the backend's CORS allow-list accordingly (see the [backend self-hosting guide](/docs/getting-started/self-hosting-guide) and [configuration reference](/docs/getting-started/configuration)). * Login and signup (`/auth/login`, `/auth/signup`) are public endpoints: they don't require an API key. After you log in, the dashboard authenticates every other request with the session token from that login, not with a Flexprice API key. You only need an API key (e.g. for testing with `curl`) if you're calling the API directly, outside the dashboard. *** ## Troubleshooting `VITE_APP_ENV` is not set to `self-hosted`, or `VITE_AUTH_PROVIDER` is still `supabase` (the `.env.example` default). Set both explicitly and rebuild, since Vite environment variables are baked in at build time, not read at container start. Confirm `VITE_APP_ENV=self-hosted` was set at build time. If it wasn't, sign-up falls back to the Supabase flow and waits for an email confirmation that will never arrive on a self-hosted instance with no email provider configured. The web server isn't falling back to `index.html` for unknown paths. Use the bundled `nginx.conf`, or add an equivalent SPA fallback rule to your own server config. 1. Confirm `VITE_API_URL` is reachable from your browser, not just from the container network: ```bash theme={null} curl /health ``` 2. Check the browser console for a CORS error. If present, add the frontend's origin to the backend's CORS configuration. 3. Remember Vite bakes `VITE_*` variables in at build time. Changing `.env` after `npm run build` requires rebuilding. Vite environment variables are compiled into the static bundle at build time, they aren't read at runtime. Rebuild the image (`docker compose up -d --build`) or rerun `npm run build`, then restart the container or server. ```bash theme={null} docker compose logs -f app docker compose ps ``` The bundled healthcheck curls `http://localhost:3000` inside the container; a non-2xx response usually means the build failed or the app crashed on start. Check the logs above for the actual error. ## Need help? * Check our [GitHub Issues](https://github.com/flexprice/flexprice-front/issues) for similar problems * Join our [Slack community](https://join.slack.com/t/flexpricecommunity/shared_invite/zt-39uat51l0-n8JmSikHZP~bHJNXladeaQ) for real-time support * Contact us at [support@flexprice.io](mailto:support@flexprice.io) ## Additional resources Run the Flexprice API and infrastructure Complete list of Flexprice environment variables Learn how to contribute to the frontend Visit our official website # Self-hosting Flexprice Source: https://docs.flexprice.io/docs/getting-started/self-hosting-guide Learn how to set up and run Flexprice on your own infrastructure You can run Flexprice on your own infrastructure in several ways. We organize self-hosting by **provider** so you can pick the option that fits your environment and add more providers over time. ## Self-hosting options Run Flexprice locally or on a single server with Docker Compose. Best for development and small deployments. Deploy Flexprice on AWS with ECS, RDS, MSK, and EKS. Production-ready, scalable setup. This guide covers the backend (API and infrastructure). To run the dashboard UI, see [Self-hosting the Frontend](/docs/getting-started/self-hosting-frontend). *** ## Docker Compose The fastest way to run Flexprice on your own machine or a single server is with Docker Compose. ### Prerequisites Before you begin, make sure you have the following installed: [Golang](https://go.dev/) 1.23+ [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/) (Docker Desktop, OrbStack, or Podman Desktop all work) `make` (pre-installed on macOS/Linux; on Windows use WSL or install via [chocolatey](https://chocolatey.org/packages/make)) One of these supported platforms: * Linux-based environment * macOS (Darwin) * WSL under Windows You do **not** need `psql`, `kafka-cli`, or any other database client installed locally. All database operations run inside Docker containers via `docker compose exec`. ## Quick Setup with Docker Compose The easiest way to get started is using our automated setup command: ```bash theme={null} # Clone the repository git clone https://github.com/flexprice/flexprice cd flexprice # Set up the complete development environment make dev-setup ``` This single command takes care of everything you need to get started: 1. Starting all required infrastructure (PostgreSQL, Kafka, ClickHouse, Temporal) 2. Building the Flexprice application image 3. Running database migrations (PostgreSQL via Ent ORM + ClickHouse) and initializing Kafka topics 4. Seeding default tenant and environment data 5. Starting all Flexprice services (API, Consumer, Worker) ### Default API Key After setup, use the following key to authenticate local API requests: ``` sk_local_flexprice_test_key ``` Pass it in the `x-api-key` request header: ```bash theme={null} curl -H "x-api-key: sk_local_flexprice_test_key" http://localhost:8080/v1/customers ``` The key is pre-configured in `internal/config/config.yaml`. Entity tables are created entirely by the Ent ORM layer — the Postgres migration files only bootstrap schemas, extensions, and stored functions. ## Accessing Services Once setup is complete, you can access: [http://localhost:8080](http://localhost:8080) [http://localhost:8088](http://localhost:8088) [http://localhost:8084](http://localhost:8084) (requires `--profile dev`) [http://localhost:8123](http://localhost:8123) The Kafka UI requires the `dev` profile: ```bash theme={null} docker compose --profile dev up -d kafka-ui ``` ## Useful Commands Here are some common commands you might need during development: ```bash theme={null} make restart-flexprice ``` ```bash theme={null} make down ``` ```bash theme={null} make clean-start ``` ```bash theme={null} make build-image && make restart-flexprice ``` ```bash theme={null} make seed-db ``` ## Running Without Docker (API only) If you prefer to run the application binary directly while keeping infrastructure in Docker: ```bash theme={null} # Start required infrastructure docker compose up -d postgres kafka clickhouse temporal temporal-ui # Run migrations make migrate-postgres migrate-clickhouse migrate-ent seed-db init-kafka # Run the application locally go run cmd/server/main.go ``` ## Connection Details Use these credentials to connect to the various services: * **Host**: localhost * **Port**: 5432 * **Database**: flexprice * **Username**: flexprice * **Password**: flexprice123 * **Host**: localhost * **Port**: 9000 (native) / 8123 (HTTP) * **Database**: flexprice * **Username**: flexprice * **Password**: flexprice123 * **Bootstrap Server**: localhost:29092 * **UI**: [http://localhost:8084](http://localhost:8084) (requires `--profile dev`) ## API Documentation Flexprice provides comprehensive API documentation in OpenAPI 3.0 format. ### Setting up Postman 1. Open Postman 2. Click on **Import** in the top left 3. Select **Import File** 4. Choose `docs/swagger/swagger-3-0.json` 5. Click **Import** 6. Create a new environment for local development: | Variable | Value | | --------- | ----------------------------- | | `baseUrl` | `http://localhost:8080/v1` | | `apiKey` | `sk_local_flexprice_test_key` | Configure the collection to send `x-api-key: {{apiKey}}` as a header on every request. ## Troubleshooting If you encounter issues during setup or operation, try these troubleshooting steps: Another process (e.g. a previously running local Flexprice binary) may be bound to port 8080 and intercepting requests before Docker's port mapping. ```bash theme={null} # Find and kill the conflicting process lsof -ti :8080 | xargs kill -9 ``` Then restart the API container: ```bash theme={null} docker compose restart flexprice-api ``` The API key in your request does not match the configured key. For local development use: ```bash theme={null} curl -H "x-api-key: sk_local_flexprice_test_key" http://localhost:8080/v1/customers ``` If you changed the key in `config.yaml`, remember the config stores the **SHA-256 hash** of the raw key, not the key itself. The middleware hashes the incoming key before lookup. You may see log lines like: ``` Failed to create Redis client: dial tcp [::1]:6379: connect: connection refused ``` This is **non-fatal** for local development. Redis is optional — it powers response caching, which falls back gracefully when unavailable. The API continues to work normally. All entity tables (`customers`, `plans`, `subscriptions`, etc.) are managed exclusively by the **Ent ORM** migration layer. The Postgres migration files (`migrations/postgres/`) only create: * Schemas and the `uuid-ossp` extension (`V0__init.sql`) * Stored functions for invoice/billing sequences (`V2_invoice_sequences.up.sql`) If you see `relation does not exist` errors, ensure you ran `make migrate-ent` after starting Postgres: ```bash theme={null} make migrate-ent make seed-db ``` 1. Ensure Docker is running properly: ```bash theme={null} docker info ``` 2. Check the status of all containers: ```bash theme={null} docker compose ps ``` 3. View logs for a specific service: ```bash theme={null} docker compose logs -f flexprice-api docker compose logs -f flexprice-consumer ``` 4. If containers are in a bad state, do a full clean restart: ```bash theme={null} docker compose down -v make dev-setup ``` 1. Verify Kafka is running: ```bash theme={null} docker compose logs kafka ``` 2. Check that all required topics exist: ```bash theme={null} docker compose exec kafka kafka-topics --bootstrap-server kafka:9092 --list ``` 3. Re-initialize topics if any are missing: ```bash theme={null} make init-kafka ``` ClickHouse migrations use standard `MergeTree` engine for local/single-node compatibility. If you see engine-related errors, ensure you are running the open-source ClickHouse image (not ClickHouse Cloud) as specified in `docker-compose.yml`. To re-run ClickHouse migrations: ```bash theme={null} make migrate-clickhouse ``` ## Need Help? If you're still experiencing issues after trying the troubleshooting steps, please: * Check our [GitHub Issues](https://github.com/flexprice/flexprice/issues) for similar problems * Join our [Slack community](https://join.slack.com/t/flexpricecommunity/shared_invite/zt-39uat51l0-n8JmSikHZP~bHJNXladeaQ) for real-time support * Contact us at [support@flexprice.io](mailto:support@flexprice.io) ## Additional Resources Learn how to contribute to Flexprice Explore our API documentation Our community guidelines Visit our official website # Invoice Calculation Order Source: https://docs.flexprice.io/docs/invoices/calculation The exact sequence flexprice uses to calculate invoice totals: line items, discounts, wallet credits, tax, and final amount. Every invoice flexprice generates follows the same deterministic sequence. Getting this order right matters when you combine coupons at multiple levels, wallet credits, and tax rates. ## The sequence ``` 1. Subtotal = sum of all line item amounts (usage charges + flat fees) 2. Line-item coupon discounts Each targeted line item's amount is reduced individually. 3. Invoice-level (subscription) coupon discounts Applied to the running subtotal in association order. Each coupon sees the total left after the previous coupon. 4. Prepaid wallet credits Deducted after all coupon discounts. 5. Taxable amount = MAX(subtotal - all_coupon_discounts, 0) Floored at zero. Coupons cannot create a negative taxable base. 6. Tax = sum of all applicable tax rates x taxable_amount Each rate is applied independently to the same taxable amount. 7. Invoice total = subtotal - total_coupon_discounts - wallet_credits_applied + tax ``` ## Worked example | Line item | Amount | | ------------------------ | ------------ | | Usage charge (API calls) | \$80.00 | | Flat monthly fee | \$20.00 | | **Subtotal** | **\$100.00** | | Discount step | Change | Running total | | ----------------------------------- | -------- | ------------- | | Line-item coupon: 25% off API calls | -\$20.00 | \$80.00 | | Subscription coupon: \$10 off | -\$10.00 | \$70.00 | | Prepaid wallet credits | -\$5.00 | \$65.00 owed | | Tax step | Amount | | ------------------------------------------------- | ----------- | | Taxable amount (subtotal - coupon discounts only) | \$70.00 | | Tax at 8.25% | +\$5.78 | | **Invoice total** | **\$70.78** | Wallet credits reduce the amount owed but do **not** reduce the taxable base. Tax is calculated on `subtotal - coupon_discounts`, not on `subtotal - coupon_discounts - wallet_credits`. ## Coupon chaining Multiple subscription-level coupons apply sequentially. Each coupon sees the amount remaining after the previous one, not the original subtotal. ``` Subtotal: $100.00 Coupon A (10%): - $10.00 --> running total: $90.00 Coupon B (10%): - $9.00 --> running total: $81.00 (applies to $90, not $100) ``` Two 10% coupons produce a 19% effective discount, not 20%. ## Line-item vs subscription-level coupons | Coupon type | What it targets | When it runs | | ------------------------- | -------------------------------------- | ------------------------------------------ | | Line-item coupon | One specific charge within the invoice | Step 2, before any subscription-level math | | Subscription-level coupon | The invoice subtotal | Step 3, after all line-item discounts | A subscription-level coupon sees a subtotal that has already been reduced by any line-item coupons. ## Multiple tax rates When multiple tax associations apply to the same invoice, each rate is applied to the same taxable amount independently. They do not compound. ``` Taxable amount: $100.00 State tax (6%): + $6.00 Federal tax (2%): + $2.00 Total tax: $8.00 (not $8.12 from compounding) ``` ## Zero-floor rule If coupons collectively exceed the subtotal, the taxable amount is clamped to zero. Excess discount is not carried forward to the next invoice period. ``` Subtotal: $50.00 Coupon A: -$30.00 Coupon B: -$30.00 ------- Taxable: $0.00 (clamped, not -$10.00) Tax: $0.00 ``` ## See also * [Coupons](/docs/product-catalogue/coupons/overview) * [Taxes](/docs/product-catalogue/taxes/overview) * [Wallets](/docs/wallet/create) # Managing Invoices Source: https://docs.flexprice.io/docs/invoices/managing **Viewing Invoices** * Navigate to **Customer Management > Invoices**. Invoices Section * View the list of invoices with details like **Invoice ID, Amount, Invoice Status, Customer Slug, Payment Status, and Due Date**. * Click on an invoice to view more details. * If you want to access Invoice for individual customers, you can click on the customer and navigate to Customers section. **Invoice Details Breakdown** When viewing a specific invoice, you will see: * **Invoice Number** – A unique identifier for the invoice. * **Date of Issue** – The date the invoice was generated. * **Due Date** – The deadline for payment. * **Order Details** – Breakdown of subscription charges, intervals, and quantities. * **Taxes and Discounts** – Any applied tax rates, credits, or discounts. * **Payment Status** – Indicates whether payment is pending, successful, or failed. * **Wallet Application** – If a customer wallet is used to partially or fully pay the invoice. **Updating Invoice Status** * Click the **Options (⋮)** menu on an invoice. * Select **Update Invoice Status**. Invoice List * Choose from: Invoice Details * **Draft** – Editable invoice before finalization. * **Finalized** – Locked invoice ready for payment processing. * **Void** – Cancels the invoice and prevents further changes. * Click **Update** to save the changes. ### Updating Payment Status 1. Open the invoice and click the **Options (⋮)** menu. 2. Select **Update Payment Status**. 3. Choose from: Download PDF * **Pending** – Payment is awaited. * **Successful** – Invoice is marked as paid. * **Failed** – Payment attempt was unsuccessful. 4. Click **Update** to apply changes. ### Downloading Invoices 1. Open an invoice. 2. Click **Download** to get a PDF version. Invoice Actions # Overview Source: https://docs.flexprice.io/docs/invoices/overview Invoices in Flexprice provide a structured way to bill customers for their subscriptions, usage, and additional charges. The invoicing system supports automated generation, status management, and payment tracking, ensuring a seamless billing process for businesses. Invoice Overview Invoices are essential for: * Tracking revenue and outstanding payments. * Maintaining financial records for compliance. * Enabling clear and transparent billing for customers. Flexprice allows you to create, manage, and update invoices directly from the dashboard or through the API. **Invoice Lifecycle** Invoices progress through the following states: * **Draft** – Created but not yet finalized; can be edited. * **Finalized** – Locked and ready for payment processing. * **Paid** – The customer has successfully made the payment. * **Failed** – Payment attempt failed (e.g., insufficient funds, expired card). * **Void** – The invoice is canceled and will not be processed further. # Partial payments Source: https://docs.flexprice.io/docs/invoices/partial-payments Flexprice supports partial payments for invoices, allowing businesses to configure whether invoices can be partially settled using available credits. This ensures a flexible payment process for customers who may not have enough credits to cover the entire invoice. By enabling partial payments, businesses can: * Automatically deduct available credits from an invoice before charging the remaining balance. * Allow invoices to be settled partially if the customer doesn’t have enough credits. Important point to note is: * Partial payments only apply to invoices where credits are eligible for that payment. * If partial payments are disabled, invoices remain unpaid (pending) until the full amount is covered **How to enable patial payments using Flexprice** * **When partial payments are allowed** * If a customer **doesn’t have enough credits** to cover the full invoice, the available credits will be **partially applied**. * The remaining balance can be settled via the customer’s **default payment method** or remain as **outstanding**. * The invoice will reflect **partial payment status** until fully paid. * **When partial payments are not allowed** * The invoice remains in **Finalized** state with a **Pending payment status** until the **full amount is paid**. * No credits are applied unless the available credits cover the **entire invoice amount**. # Apply Coupon to a Subscription Source: https://docs.flexprice.io/docs/product-catalogue/coupons/apply-discount-on-subscription Link coupons to subscriptions at creation or post-creation. Use subscription phases to schedule discount windows. You can attach coupons at subscription creation time. For post-creation changes (add, remove, or schedule a future removal), use the subscription modification API. ## Before you start The coupon must be: * Status: `active` * Within its `redeem_after` / `redeem_before` window (if set) * Below its `max_redemptions` limit (if set) Validation runs at association time. If any condition fails, you get a validation error before the association is created. ## At subscription creation Pass coupons in the `subscription_coupons` array. Each entry takes a `coupon_code` (the human-readable code from the coupon object), optional `start_date`/`end_date`, and an optional `price_id` to target a specific line item. ### Subscription-level coupon Discounts the full invoice subtotal. Omit `price_id`: ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "customer_id": "", "plan_id": "", "start_date": "2025-01-01T00:00:00Z", "subscription_coupons": [ { "coupon_code": "SUMMER15" } ] }' ``` `start_date` defaults to the subscription start date. `end_date` is optional: omit it to let the coupon run until its own `cadence` ends or the subscription cancels. ### Line-item coupon Discounts one specific price in the plan only. Set `price_id` to the target price: ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "customer_id": "", "plan_id": "", "start_date": "2025-01-01T00:00:00Z", "subscription_coupons": [ { "coupon_code": "USAGE10", "price_id": "" } ] }' ``` ## Scheduled discounts via phases Subscription phases are the recommended way to apply time-bounded coupons. Each phase has its own `start_date` and optional `end_date`. Coupons in a phase inherit those dates as their association window. ### Example: introductory discount for Q1 only ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "customer_id": "", "plan_id": "", "start_date": "2025-01-01T00:00:00Z", "phases": [ { "start_date": "2025-01-01T00:00:00Z", "end_date": "2025-03-31T23:59:59Z", "subscription_coupons": [ { "coupon_code": "Q1PROMO" } ] }, { "start_date": "2025-04-01T00:00:00Z" } ] }' ``` The coupon is active during Phase 1 only. Phase 2 has no coupon, so invoices from April onwards are at full price. ### Example: line-item discount scoped to a phase ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "customer_id": "", "plan_id": "", "start_date": "2025-01-01T00:00:00Z", "phases": [ { "start_date": "2025-01-01T00:00:00Z", "end_date": "2025-06-30T23:59:59Z", "subscription_coupons": [ { "coupon_code": "USAGE10", "price_id": "" } ] }, { "start_date": "2025-07-01T00:00:00Z" } ] }' ``` ## Common scheduling patterns | Goal | Phase configuration | | ---------------------------------------- | ---------------------------------------------------------------------------------- | | Discount for first 3 months | Phase 1: `end_date` = 3 months out, `coupons: [id]`. Phase 2: no coupon. | | Discount starting 6 months in | Phase 1: no coupon. Phase 2: `start_date` = 6 months in, `coupons: [id]`. | | Remove a discount on a known future date | Phase 1: today to removal date with coupon. Phase 2: from removal date, no coupon. | | Discount on one charge only | In `subscription_coupons`, set `price_id` to the target price ID. | ## Add or remove a coupon after subscription creation Use the subscription modification API with `type: "coupon"`. ### Add a coupon ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions//modify/execute \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "type": "coupon", "coupon_params": { "action": "add", "coupon_code": "", "start_date": "2025-06-01T00:00:00Z", "end_date": "2025-08-31T23:59:59Z" } }' ``` `start_date` defaults to now if omitted. `end_date` is optional: omit it to apply the coupon indefinitely. To target a specific line item instead of the full invoice, add `subscription_line_item_id` (mutually exclusive with `subscription_id`): ```bash theme={null} { "type": "coupon", "coupon_params": { "action": "add", "coupon_code": "", "start_date": "2025-06-01T00:00:00Z", "subscription_line_item_id": "" } } ``` ### Remove a coupon ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions//modify/execute \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "type": "coupon", "coupon_params": { "action": "remove", "coupon_association_id": "" } }' ``` Get the `coupon_association_id` from the subscription response with `expand=coupon_associations`. Removal affects only invoices generated after the request. Previously issued invoices are not changed. ### Preview before executing ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions//modify/preview \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "type": "coupon", "coupon_params": { "action": "add", "coupon_code": "", "start_date": "2025-06-01T00:00:00Z" } }' ``` ## subscription\_coupons fields (at creation) | Field | Type | Required | Description | | ------------- | --------- | -------- | ------------------------------------------------------------------------------ | | `coupon_code` | string | Yes | The coupon's `code` value (case-insensitive) | | `start_date` | timestamp | No | When the association starts. Defaults to the subscription or phase start date. | | `end_date` | timestamp | No | When the association ends. Omit for indefinite. | | `price_id` | string | No | Target a specific line item. Omit for subscription-level. | ## coupon\_params fields (post-creation modification) | Field | Type | Required | Description | | --------------------------- | --------- | -------------------- | ----------------------------------------------------------------------- | | `action` | string | Yes | `add` or `remove` | | `coupon_code` | string | When `action=add` | Code of the coupon to attach | | `coupon_association_id` | string | When `action=remove` | ID of the coupon association to detach | | `start_date` | timestamp | No | When the association starts. Defaults to now. | | `end_date` | timestamp | No | When the association ends. Omit for indefinite. | | `subscription_line_item_id` | string | No | Target a specific line item. Mutually exclusive with `subscription_id`. | ## How discounts appear on invoices Discount amounts are tracked at two levels on each line item: | Field | Description | | ------------------------ | ------------------------------------------------------------------------------------------ | | `line_item_discount` | Discount applied directly to this line item (from a line-item coupon targeting this price) | | `invoice_level_discount` | Invoice-wide coupon discount prorated to this line item | At the invoice level, `total_discount` is the sum of all coupon discounts across all line items. The discount terms are snapshotted when the coupon is applied, so they are preserved even if the coupon definition is later updated. ## Check active coupons on a subscription ```bash theme={null} curl "https://api.flexprice.io/v1/subscriptions/?expand=coupon_associations" \ -H "x-api-key: " ``` ## Validation errors | Error | Cause | | ------------------------- | -------------------------------------- | | `coupon_not_active` | Coupon is not in `active` status | | `coupon_expired` | Current time is after `redeem_before` | | `coupon_not_yet_valid` | Current time is before `redeem_after` | | `max_redemptions_reached` | `total_redemptions >= max_redemptions` | # Create a Coupon Source: https://docs.flexprice.io/docs/product-catalogue/coupons/create Create percentage or fixed-amount discount coupons via API or dashboard. ## Via API ```bash theme={null} curl -X POST https://api.flexprice.io/v1/coupons \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "name": "Summer Sale", "type": "percentage", "percentage_off": "15.00", "cadence": "once", "coupon_code": "SUMMER15", "redeem_after": "2025-07-01T00:00:00Z", "redeem_before": "2025-08-31T23:59:59Z", "max_redemptions": 1000 }' ``` **Response** ```json theme={null} { "id": "coup_01abc123", "name": "Summer Sale", "type": "percentage", "percentage_off": "15.00", "cadence": "once", "coupon_code": "SUMMER15", "redeem_after": "2025-07-01T00:00:00Z", "redeem_before": "2025-08-31T23:59:59Z", "max_redemptions": 1000, "total_redemptions": 0, "status": "active" } ``` ## Request fields | Field | Type | Required | Description | | --------------------- | -------------- | ----------------------- | ------------------------------------------------------------------------------------------------- | | `name` | string | Yes | Display name shown in the dashboard and invoice details | | `type` | string | Yes | `percentage` or `fixed` | | `cadence` | string | Yes | `once`, `repeated`, or `forever` | | `percentage_off` | decimal string | When `type=percentage` | Discount as a percent, e.g. `"15.00"` for 15% | | `amount_off` | decimal string | When `type=fixed` | Flat discount amount, e.g. `"50.00"` | | `currency` | string | When `type=fixed` | ISO currency code, e.g. `"USD"` | | `duration_in_periods` | int | When `cadence=repeated` | Number of billing cycles the discount applies. Must not be set for other cadences. | | `coupon_code` | string | No | Human-readable code customers can enter to redeem (e.g. `"SUMMER15"`). Auto-generated if omitted. | | `redeem_after` | timestamp | No | Earliest time a new association can be created | | `redeem_before` | timestamp | No | Expiry. New associations cannot be created after this time. | | `max_redemptions` | int | No | Cap on total associations across all subscriptions | | `metadata` | object | No | Key-value pairs for your own tracking | ## Cadence behaviour | Cadence | What happens | | ---------- | --------------------------------------------------------------------- | | `once` | Discount applies to the first invoice in the association window only | | `repeated` | Discount applies for `duration_in_periods` consecutive billing cycles | | `forever` | Discount applies to every invoice for the life of the subscription | ## Fixed-amount coupon For a fixed dollar amount off, set `type: fixed` and provide both `amount_off` and `currency`: ```bash theme={null} curl -X POST https://api.flexprice.io/v1/coupons \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "name": "New Customer Credit", "type": "fixed", "amount_off": "50.00", "currency": "USD", "cadence": "once" }' ``` ## Recurring coupon To discount multiple billing cycles, use `cadence: repeated` with `duration_in_periods`: ```bash theme={null} curl -X POST https://api.flexprice.io/v1/coupons \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "name": "Quarterly Promo", "type": "percentage", "percentage_off": "20.00", "cadence": "repeated", "duration_in_periods": 3 }' ``` This applies a 20% discount for the first 3 billing cycles of each association. ## Manage existing coupons ```bash theme={null} # List coupons curl https://api.flexprice.io/v1/coupons \ -H "x-api-key: " # Get a coupon curl https://api.flexprice.io/v1/coupons/ \ -H "x-api-key: " # Search coupons by status curl -X POST https://api.flexprice.io/v1/coupons/search \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "status": "active" }' # Update a coupon (only name and metadata are updatable) curl -X PUT https://api.flexprice.io/v1/coupons/ \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "name": "Summer Sale 2025" }' # Delete a coupon curl -X DELETE https://api.flexprice.io/v1/coupons/ \ -H "x-api-key: " ``` ## Via dashboard Go to **Product Catalog** > **Coupons** > **Create Coupon** and fill in the same fields described above. ## Next step [Apply the coupon to a subscription](/docs/product-catalogue/coupons/apply-discount-on-subscription) at creation time or post-creation via the subscription modification API. # Coupons Overview Source: https://docs.flexprice.io/docs/product-catalogue/coupons/overview Create percentage or fixed-amount discounts and apply them to subscriptions at the invoice or line-item level. A coupon is a reusable discount definition. You create it once, then link it to one or more subscriptions via an **association**. Each association can be scoped to a time window and optionally targeted at a specific line item rather than the whole invoice. ## Coupon types | Type | Discount field | Currency required | Example | | ------------ | ---------------- | ----------------- | ------------------- | | `percentage` | `percentage_off` | No | 15% off the invoice | | `fixed` | `amount_off` | Yes | \$50 off | ## Cadence Cadence controls how many billing cycles the discount repeats within an association's active window. | Cadence | Behavior | | ---------- | ------------------------------------------------------------ | | `once` | Applied to the first invoice of the association period only | | `repeated` | Applied for `duration_in_periods` consecutive billing cycles | | `forever` | Applied to every invoice for the life of the subscription | ## Validity controls These fields live on the coupon itself and gate whether a new association can be created. | Field | Purpose | | ----------------- | ---------------------------------------------------------------------------------------------------------------------------- | | `redeem_after` | Earliest timestamp a coupon can be associated to a subscription | | `redeem_before` | Expiry timestamp. Associations cannot be created after this date. Existing associations created before expiry remain active. | | `max_redemptions` | Maximum total associations allowed across all subscriptions | The `total_redemptions` counter increments when the association is **created**, not when an invoice is generated. ## Coupon scope **Subscription level**: discount applies to the invoice subtotal (or the running subtotal after earlier coupons have applied). **Line-item level**: discount applies to a specific price within the invoice before any subscription-level coupons run. ## How multiple coupons apply When an invoice has both line-item and subscription-level coupons: 1. Line-item coupons apply first, reducing each targeted line item's amount individually. 2. Subscription-level coupons apply next, in association order, each seeing the subtotal left after the previous coupon. 3. Wallet credits are deducted after all coupon discounts. 4. Tax is calculated on `MAX(subtotal - all_discounts, 0)`. Tax is never charged on discounted-away amounts. Two 20% subscription-level coupons do not produce a 40% total discount. The second coupon applies to the amount remaining after the first. See [Billing Calculation Order](/docs/invoices/billing-calculation-order) for the full worked example. ## Coupon statuses | Status | Meaning | | ---------- | ------------------------------------- | | `draft` | Created but not yet usable | | `active` | Can be associated to subscriptions | | `archived` | No longer usable for new associations | Only `active` coupons pass association validation. ## Quick start 1. [Create a coupon](/docs/product-catalogue/coupons/create) with a type, value, and optional validity window. 2. [Apply it to a subscription](/docs/product-catalogue/coupons/apply-discount-on-subscription) at creation time or via subscription phases for scheduled windows. # Pricing unit Source: https://docs.flexprice.io/docs/product-catalogue/custom-pricing/pricing-unit This feature allows you to create and use your own currency in Flexprice. With create Price Units, add them to plans and wallets in custom currency. **Custom pricing** allows you to price features using your own unit (such as credits) instead of only standard currencies like USD or EUR. ### What is custom currency? **Custom currency** is pricing in your own unit (such as credits) instead of a standard money currency like USD or EUR. You set and show prices in that unit-for example, "10 credits per month"-and **Flexprice converts those amounts to your base currency (e.g. USD) for billing**, This ensures a consistent currency for accounting, even though the feature is shown in credits. *** ## How it works internally All billing calculations are performed using the pricing unit’s base currency (for example, USD). Amounts are defined and displayed in the **pricing unit** (e.g. credits), Conversion happens only at price or wallet creation; Flexprice uses the base currency for all calculations. In other words: input and display use the pricing unit; computation, persistence, and invoicing use base currency. Conversion uses the formula **amount in your unit × conversion\_rate = amount in base currency**. Rounding and precision (e.g. two decimals for USD, zero for JPY) are applied only when **invoice charges are computed**, not at conversion or when creating prices or price units. Wallet balance is always shown in **base currency and credits**, whether the wallet was created with fiat or a custom price unit. Flexprice **stores and bills** everything in the base currency (e.g. USD). *** ## Using custom currency 1. **Create** a Price Unit (dashboard or API, below). 2. **Use in plans** — add charges or create prices in your unit; see [Use cases](/docs/product-catalogue/custom-pricing/use-cases#use-custom-currency-in-plans-and-prices). 3. **Use in wallets** — create a wallet in your unit; see [Use cases](/docs/product-catalogue/custom-pricing/use-cases). *** ## Use cases * **Credits or tokens** - Sell in credits or tokens; show balance and charges in that unit while billing and storing in base currency (e.g. USD). * **Multi-currency display, single-currency billing** - Present prices in a custom unit per region or product while retaining one base currency for accounting and payouts. *** ## Concepts and terminology **Custom currency / Price Unit** - Your own pricing unit (e.g. credits) used instead of fiat currency. Defined once as a Price Unit and reused across plans, prices, and wallets, with automatic conversion to the base currency for billing. | Field | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | **code** | **3-character** identifier (e.g. `CRD`, `TOK`, `fpc`) used when referencing the unit in a price or wallet. Must be unique per environment when Active. | | **symbol** | Display symbol shown in the UI and on invoices (e.g. ¢, ==). | | **base\_currency** | The standard currency the unit is pegged to (e.g. `usd`). All billing and storage use this currency. | | **conversion\_rate** | Multiplier from the custom unit to base currency. | **Conversion formula** ```text theme={null} amount in your unit × conversion_rate = amount in base currency ``` **Example:** If 1 credit = 0.01 USD, set `conversion_rate = "0.01"`. Then 100 credits = 1.00 USD. **Price unit types: FIAT vs CUSTOM** * **FIAT** - Amounts are in a standard currency (USD, EUR, etc.). No conversion is applied. * **CUSTOM** - Amounts are in a Price Unit. Flexprice converts to base currency for billing; you configure and display values in the custom unit. *** ## Create a Price Unit (dashboard) Go to **Product Catalog** → **Price Units** → **Add** (or **Create Price Unit**). Enter **Name**, **Code**, **Symbol**, **Base currency**, and **Conversion rate**.\ Example: 100 units = 1 USD → use conversion rate **0.01**. Create Price Unit *** ## Create a Price Unit (API) Send `POST /v1/prices/units` with a JSON body (you can see example below). Include **name**, **code**, **symbol**, **base\_currency**, and **conversion\_rate**. Optionally **metadata**. **Example request** ```json theme={null} { "name": "Credits", "code": "CRD", "symbol": "¢", "base_currency": "usd", "conversion_rate": "0.01", "metadata": {} } ``` Conversion, 100 CRD = 1.00 USD. Base URL options: `https://us.api.flexprice.io/v1` or `https://api.cloud.flexprice.io/v1` (check out the [Create a price unit](/api-reference/price-units/create-price-unit)). *** ## Use custom currency in plans and prices You can add charges or create prices in your custom unit from the **dashboard** or via the **API**. In both cases you choose your Price Unit and enter amounts in that unit; Flexprice converts to base currency for billing. ### Dashboard Go to **Product Catalog** → **Plans** → open the plan you want to edit. In **Charges**, click **+ Add** (or **Edit** then add). In the **Currency** dropdown choose **Custom** and select your Price Unit. Add recurring or usage-based charges and enter amounts in your unit (e.g. 15 credits/month). Click **Save**. For more detail and screenshots, see [Use cases: add charges and wallet](/docs/product-catalogue/custom-pricing/use-cases). ### API In the create-price request (`POST /v1/prices`), set **price\_unit\_type** to **CUSTOM** and include **price\_unit\_config**. In **price\_unit\_config** set **price\_unit** (your unit’s code, e.g. `CRD`). For a fixed amount use **amount**; for tiered pricing use **price\_unit\_tiers** only (not the top-level **tiers** field). **Example: fixed monthly price in credits** The request must also include **type** (e.g. `FIXED`), **billing\_cadence** (e.g. `RECURRING`), and **invoice\_cadence** (e.g. `ADVANCE` or `ARREAR`). Below shows the custom-pricing–relevant fields: ```json theme={null} { "currency": "usd", "price_unit_type": "CUSTOM", "type": "FIXED", "billing_model": "FLAT_FEE", "billing_period": "MONTHLY", "billing_cadence": "RECURRING", "invoice_cadence": "ADVANCE", "entity_type": "PLAN", "entity_id": "your-plan-id", "price_unit_config": { "price_unit": "CRD", "amount": "100.00" } } ``` With `conversion_rate = "0.01"`, 100 CRD = 1.00 USD. Flexprice stores both the custom amount and the base-currency amount and uses base currency for billing. **Billing models** | Model | In price\_unit\_config | Notes | | --------- | --------------------------------------- | -------------------------------------------------------------------------------------- | | FLAT\_FEE | **price\_unit**, **amount** | Fixed price in your unit. | | PACKAGE | **price\_unit**, **amount** | **transform\_quantity** is required; for usage add **meter\_id**. | | TIERED | **price\_unit**, **price\_unit\_tiers** | Use **price\_unit\_tiers** only, not top-level **tiers**. For usage add **meter\_id**. | *** Refer to the API Reference and Price Unit documentation. check out the [API Reference](/api-reference/introduction) and the [Price Unit](/api-reference/price-units/list-price-units). # Use cases Source: https://docs.flexprice.io/docs/product-catalogue/custom-pricing/use-cases Add charges and create wallets using custom price units. All billing calculations use base currency. ## Add charges in custom currency **Product Catalog** → **Plans** → select a plan (e.g. Pro). In the **Charges** section click **+ Add** (or **Edit** then add charges). You’ll see **Add Charges to \[plan]**. In the **Currency** dropdown choose **Custom**, then select your price unit (e.g. **fpc (==)** — 1 fpc = 1.27 USD). Standard options (USD, INR, etc.) stay under **Standard**. **Add Recurring Charges** — fixed amount per period (e.g. 15 fpc/month). **Add Usage Based Charges** — charge by usage in the custom unit. Set **Display Name** for the charge (e.g. feature name). Enter the **value** in the custom unit. Currency dropdown - Custom and Standard Add Recurring and Usage Based charges *** ## Use custom currency in plans and prices You can add charges from the **dashboard** (see above) or create prices via the **API** below. In both cases you choose your Price Unit and enter amounts in that unit; Flexprice converts to base currency for billing. ### API In the create-price request (`POST /v1/prices`), set **price\_unit\_type** to **CUSTOM** and include **price\_unit\_config**. In **price\_unit\_config** set **price\_unit** (your unit's code, e.g. `CRD`). For a fixed amount use **amount**; for tiered pricing use **price\_unit\_tiers** only (not the top-level **tiers** field). **Example: fixed monthly price in credits** The request must also include **type** (e.g. `FIXED`), **billing\_cadence** (e.g. `RECURRING`), and **invoice\_cadence** (e.g. `ADVANCE` or `ARREAR`). Below shows the custom-pricing–relevant fields: ```json theme={null} { "currency": "usd", "price_unit_type": "CUSTOM", "type": "FIXED", "billing_model": "FLAT_FEE", "billing_period": "MONTHLY", "billing_cadence": "RECURRING", "invoice_cadence": "ADVANCE", "entity_type": "PLAN", "entity_id": "your-plan-id", "price_unit_config": { "price_unit": "CRD", "amount": "100.00" } } ``` With `conversion_rate = "0.01"`, 100 CRD = 1.00 USD. Flexprice stores both the custom amount and the base-currency amount and uses base currency for billing. **Billing models** | Model | In price\_unit\_config | Notes | | --------- | --------------------------------------- | -------------------------------------------------------------------------------------- | | FLAT\_FEE | **price\_unit**, **amount** | Fixed price in your unit. | | PACKAGE | **price\_unit**, **amount** | **transform\_quantity** is required; for usage add **meter\_id**. | | TIERED | **price\_unit**, **price\_unit\_tiers** | Use **price\_unit\_tiers** only, not top-level **tiers**. For usage add **meter\_id**. | For more on fields and endpoints, see the [API Reference](/api-reference/introduction) and [Price Unit](/api-reference/price-units/list-price-units). *** ## Create Wallet using custom pricing unit For a wallet that uses a **custom price unit**, Flexprice does not perform any conversion at wallet creation: it uses the **price unit’s conversion rate** as-is. If you pass **conversion\_rate** in the create-wallet API request, it is **ignored** when **price\_unit** is provided; the price unit’s conversion rate is always used. The wallet’s currency and conversion rate come only from the selected price unit. Go to **Customers** → select the customer → open the **Wallets** tab (or the section where you manage wallets). Click **+ Add** or **Create Wallet** to open the Create Wallet dialog. In the **Currency** dropdown, choose **Custom** (not Standard), then select your price unit (e.g. **fpc (==)** or **CRD (¢)**). Standard options (USD, INR, etc.) are listed under **Standard**. When you select a custom price unit, **Currency** and **Conversion rate** are set automatically from that unit’s base currency and conversion rate. You don’t need to enter them manually. Optional: set **Initial credits to load**, **Wallet type** (Pre-Paid / Post-Paid), or **Alert** settings. Wallet in custom currency **API:** To create a wallet in custom currency via the API, include **price\_unit** (the unit’s **code**, e.g. `fpc` or `CRD`) in the request body. Flexprice sets the wallet’s currency to the price unit’s base currency and its conversion rate from the price unit. The price unit must be **Active**; otherwise the request fails validation. *** ## Summary * **Create** a price unit first (Product Catalog → Price Units) with a conversion rate to base currency, and ensure it’s **Active**. * **Use** it in a plan: open the plan → **Charges** → **+ Add** → **Currency** → **Custom** → select your unit → add recurring or usage-based charges → **Save**. * **Use** it in a wallet: open the customer → **Wallets** → **Create Wallet** → **Currency** → **Custom** → select your unit → create. Balance is always shown in **base currency and credits** only, irrespective of whether the wallet was created with fiat currency or a price unit. # AVERAGE Source: https://docs.flexprice.io/docs/product-catalogue/features/aggregation/average Calculates the average value of a specified property across all matching events. Step-by-Step Setup when creating an AVERAGE-based metered feature: 1. **Navigate to Features** * Go to Product Catalog → Features * Click "Add Feature" 2. **Basic Information** * **Name**: "Response Time" (or descriptive name) * **Type**: Select "Metered" 3. **Event Configuration** * **Event Name**: `api.response` (must match your event data) * **Aggregation Function**: Average * **Aggregation Field**: `response_time_ms` (the property to average) 4. **Usage Settings** * **Usage Reset**: Periodic (for monthly averages) or Cumulative * **Unit Name**: `ms` (milliseconds) 5. **Save Feature** ## Calculation Example ### Event Data ```json theme={null} [ { "event_id": "evt_001", "event_name": "api.response", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:00:00Z", "properties": { "response_time_ms": 10 } }, { "event_id": "evt_002", "event_name": "api.response", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:05:00Z", "properties": { "response_time_ms": 20 } }, { "event_id": "evt_003", "event_name": "api.response", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:10:00Z", "properties": { "response_time_ms": 30 } }, { "event_id": "evt_004", "event_name": "api.response", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:15:00Z", "properties": { "response_time_ms": 40 } }, { "event_id": "evt_005", "event_name": "api.response", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:20:00Z", "properties": { "response_time_ms": -1 } }, { "event_id": "evt_006", "event_name": "api.response", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:25:00Z", "properties": { "response_time_ms": 0 } } ] ``` ### Calculation Process 1. **Event Matching**: All events with `event_name = "api.response"` 2. **Deduplication**: Remove duplicate event IDs using `anyLast()` (each event has unique ID in this case) * `evt_001` → `10 ms` * `evt_002` → `20 ms` * `evt_003` → `30 ms` * `evt_004` → `40 ms` * `evt_005` → `-1 ms` * `evt_006` → `0 ms` 3. **Average Calculation**: `(10 + 20 + 30 + 40 + (-1) + 0) / 6 = 99 / 6 = 16.5 ms` **Result**: `16.5 ms` ## Use Cases ### Response Time Monitoring **Perfect for**: API response times, page load times, query performance ```json theme={null} { "event_name": "api.response", "external_customer_id": "acme_corp", "properties": { "response_time_ms": 185 } } ``` ### Performance Metrics **Perfect for**: Processing duration, execution time, latency measurements ```json theme={null} { "event_name": "job.processing", "external_customer_id": "user_456", "properties": { "duration_seconds": 45.2 } } ``` ### Business Analytics **Perfect for**: Order values, session lengths, transaction amounts ```json theme={null} { "event_name": "order.placed", "external_customer_id": "merchant_789", "properties": { "order_value": 125.50 } } ``` ### System Monitoring **Perfect for**: CPU usage, memory consumption, bandwidth utilization ```json theme={null} { "event_name": "system.metrics", "external_customer_id": "customer_101", "properties": { "cpu_percent": 65.8 } } ``` ### File Size Analysis **Perfect for**: Average file sizes, document lengths, data volumes ```json theme={null} { "event_name": "file.processed", "external_customer_id": "data_user", "properties": { "file_size_mb": 12.7 } } ``` ### When to Use AVERAGE ✅ **Use AVERAGE when:** * Need mean values over time periods * Tracking performance or quality metrics * Measuring typical behavior or usage patterns * Billing based on average consumption levels ## Next Steps * **[Creating a Metered Feature](/docs/event-ingestion/creating-a-metered-feature)** - Complete setup guide * **[Sending Events](/docs/event-ingestion/sending-events)** - How to transmit AVERAGE events # COUNT Source: https://docs.flexprice.io/docs/product-catalogue/features/aggregation/count Counts the number of distinct events (by ID) that match the criteria. Step-by-Step Setup when creating a COUNT-based metered feature: 1. **Navigate to Features** * Go to Product Catalog → Features * Click "Add Feature" 2. **Basic Information** * **Name**: "API Calls" (or descriptive name) * **Type**: Select "Metered" 3. **Event Configuration** * **Event Name**: `api.calls` (must match your event data) * **Aggregation Function**: Count * **Aggregation Field**: Leave empty (not required for COUNT) 4. **Usage Settings** * **Usage Reset**: Periodic (for monthly limits) or Cumulative * **Unit Name**: `API call / API calls` 5. **Save Feature** ## Calculation Example ### Event Data ```json theme={null} [ { "event_id": "evt_001", "event_name": "api.calls", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:00:00Z" }, { "event_id": "evt_002", "event_name": "api.calls", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:05:00Z" }, { "event_id": "evt_001", // Duplicate event ID "event_name": "api.calls", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:10:00Z" } ] ``` ### Calculation Process 1. **Event Matching**: All events with `event_name = "api.calls"` 2. **Deduplication**: Remove duplicate event IDs * `evt_001` (first occurrence) * `evt_002` * `evt_001` (duplicate - ignored) 3. **Count Result**: `2` (distinct events) **Result**: `2 API calls` ## Use Cases ### API Request Tracking **Perfect for**: RESTful APIs, GraphQL queries, webhook calls ```json theme={null} { "event_name": "api.calls", "external_customer_id": "acme_corp" } ``` ### File Operations **Perfect for**: File uploads, downloads, processing tasks ```json theme={null} { "event_name": "file.uploads", "external_customer_id": "user_456" } ``` ### Transaction Counting **Perfect for**: Payment transactions, database operations, user actions ```json theme={null} { "event_name": "transactions", "external_customer_id": "merchant_789" } ``` ### Feature Usage **Perfect for**: Feature activations, button clicks, page views ```json theme={null} { "event_name": "feature.usage", "external_customer_id": "customer_101" } ``` ### When to Use COUNT ✅ **Use COUNT when:** * Tracking discrete events or actions * You don't need to measure quantity/volume * Simple occurrence-based billing * High-volume event streams (best performance) ## Next Steps * **[Creating a Metered Feature](/docs/event-ingestion/creating-a-metered-feature)** - Complete setup guide * **[Sending Events](/docs/event-ingestion/sending-events)** - How to transmit COUNT events # COUNT UNIQUE Source: https://docs.flexprice.io/docs/product-catalogue/features/aggregation/count-unique Counts the number of distinct values for a specified property. Step-by-Step Setup when creating a COUNT UNIQUE-based metered feature: 1. **Navigate to Features** * Go to Product Catalog → Features * Click "Add Feature" 2. **Basic Information** * **Name**: "Active Users" (or descriptive name) * **Type**: Select "Metered" 3. **Event Configuration** * **Event Name**: `user.activity` (must match your event data) * **Aggregation Function**: Count Unique * **Aggregation Field**: `user_id` (the property to count unique values for) 4. **Usage Settings** * **Usage Reset**: Periodic (for monthly limits) or Cumulative * **Unit Name**: `user / users` 5. **Save Feature** ## Calculation Example ### Event Data ```json theme={null} [ { "event_id": "evt_001", "event_name": "user.activity", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:00:00Z", "properties": { "user_id": "user_alice" } }, { "event_id": "evt_002", "event_name": "user.activity", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:05:00Z", "properties": { "user_id": "user_bob" } }, { "event_id": "evt_003", "event_name": "user.activity", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:10:00Z", "properties": { "user_id": "user_alice" } }, { "event_id": "evt_004", "event_name": "user.activity", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:15:00Z", "properties": { "user_id": "user_charlie" } } ] ``` ### Calculation Process 1. **Event Matching**: All events with `event_name = "user.activity"` 2. **Property Extraction**: Extract `user_id` values from each event * `evt_001` → `"user_alice"` * `evt_002` → `"user_bob"` * `evt_003` → `"user_alice"` (duplicate) * `evt_004` → `"user_charlie"` 3. **Count Unique Result**: `3` distinct users (`user_alice`, `user_bob`, `user_charlie`) **Result**: `3 users` ## Use Cases ### Active Users Tracking **Perfect for**: Monthly active users, daily active users, unique visitors ```json theme={null} { "event_name": "user.activity", "external_customer_id": "acme_corp", "properties": { "user_id": "user_12345" } } ``` ### Unique Sessions **Perfect for**: Session tracking, device identification, unique connections ```json theme={null} { "event_name": "session.activity", "external_customer_id": "user_456", "properties": { "session_id": "sess_abc123" } } ``` ### Feature Usage **Perfect for**: Unique feature users, distinct tool usage, unique resource access ```json theme={null} { "event_name": "feature.usage", "external_customer_id": "merchant_789", "properties": { "feature_name": "advanced_analytics" } } ``` ### IP Address Tracking **Perfect for**: Unique IP addresses, geographic distribution, security monitoring ```json theme={null} { "event_name": "api.request", "external_customer_id": "customer_101", "properties": { "ip_address": "192.168.1.100" } } ``` ### Device Tracking **Perfect for**: Unique devices, platform analytics, hardware usage ```json theme={null} { "event_name": "app.usage", "external_customer_id": "mobile_user", "properties": { "device_id": "device_xyz789" } } ``` ### When to Use COUNT UNIQUE ✅ **Use COUNT UNIQUE when:** * Tracking distinct users, sessions, or resources * Need to count unique values, not occurrences * Measuring reach or adoption metrics * Eliminating duplicates for billing purposes ## Next Steps * **[Creating a Metered Feature](/docs/event-ingestion/creating-a-metered-feature)** - Complete setup guide * **[Sending Events](/docs/event-ingestion/sending-events)** - How to transmit COUNT UNIQUE events # LATEST Source: https://docs.flexprice.io/docs/product-catalogue/features/aggregation/latest Returns the most recent value of a property based on event timestamp. Step-by-Step Setup when creating a LATEST-based metered feature: 1. **Navigate to Features** * Go to Product Catalog → Features * Click "Add Feature" 2. **Basic Information** * **Name**: "Current Tier" (or descriptive name) * **Type**: Select "Metered" 3. **Event Configuration** * **Event Name**: `subscription.tier` (must match your event data) * **Aggregation Function**: Latest * **Aggregation Field**: `tier_level` (the property to get latest value for) 4. **Usage Settings** * **Usage Reset**: Periodic (for current period values) or Cumulative * **Unit Name**: `tier` (descriptive unit) 5. **Save Feature** ## Calculation Example ### Event Data ```json theme={null} [ { "event_id": "evt_001", "event_name": "subscription.tier", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:00:00Z", "properties": { "tier_level": 1 } }, { "event_id": "evt_002", "event_name": "subscription.tier", "external_customer_id": "customer_123", "timestamp": "2024-01-15T12:00:00Z", "properties": { "tier_level": 2 } }, { "event_id": "evt_003", "event_name": "subscription.tier", "external_customer_id": "customer_123", "timestamp": "2024-01-15T09:00:00Z", "properties": { "tier_level": 3 } }, { "event_id": "evt_004", "event_name": "subscription.tier", "external_customer_id": "customer_123", "timestamp": "2024-01-15T14:00:00Z", "properties": { "tier_level": 4 } } ] ``` ### Calculation Process 1. **Event Matching**: All events with `event_name = "subscription.tier"` 2. **Timestamp Ordering**: Find the event with the latest timestamp * `evt_003` → `09:00:00Z` (tier\_level: 3) * `evt_001` → `10:00:00Z` (tier\_level: 1) * `evt_002` → `12:00:00Z` (tier\_level: 2) * `evt_004` → `14:00:00Z` (tier\_level: 4) **← Latest** 3. **Latest Result**: `4` (most recent tier\_level value) **Result**: `4 tier` ## Use Cases ### Current Subscription Tier **Perfect for**: Subscription levels, plan tiers, service grades ```json theme={null} { "event_name": "subscription.tier", "external_customer_id": "acme_corp", "properties": { "tier_level": 3 } } ``` ### Configuration Settings **Perfect for**: Feature flags, settings values, configuration states ```json theme={null} { "event_name": "config.update", "external_customer_id": "user_456", "properties": { "max_users": 50 } } ``` ### Status Tracking **Perfect for**: Account status, service health, operational states ```json theme={null} { "event_name": "account.status", "external_customer_id": "merchant_789", "properties": { "status_code": 2 } } ``` ### Resource Limits **Perfect for**: Current quotas, capacity limits, threshold values ```json theme={null} { "event_name": "quota.update", "external_customer_id": "customer_101", "properties": { "storage_gb_limit": 1000 } } ``` ### Version Tracking **Perfect for**: Software versions, API versions, schema versions ```json theme={null} { "event_name": "version.update", "external_customer_id": "api_user", "properties": { "api_version": 3 } } ``` ### When to Use LATEST ✅ **Use LATEST when:** * Need current state or most recent configuration * Tracking subscription tiers or plan levels * Billing based on current settings or status * Want the final value in a sequence of changes ## Next Steps * **[Creating a Metered Feature](/docs/event-ingestion/creating-a-metered-feature)** - Complete setup guide * **[Sending Events](/docs/event-ingestion/sending-events)** - How to transmit LATEST events # MAX Source: https://docs.flexprice.io/docs/product-catalogue/features/aggregation/max Get the maximum value of a property with optional bucketed or group-by calculations. Step-by-Step Setup when creating a MAX-based metered feature: 1. **Navigate to Features** * Go to Product Catalog → Features * Click "Add Feature" 2. **Basic Information** * **Name**: "Peak Concurrent Users" (or descriptive name) * **Type**: Select "Metered" 3. **Event Configuration** * **Event Name**: `concurrent.users` (must match your event data) * **Aggregation Function**: Max * **Aggregation Field**: `user_count` (the property to find maximum for) * **Bucket Size**: Optional - Leave empty for standard MAX, or select (HOUR, DAY, etc.) for bucketed MAX * **Group By**: Optional - Only used with bucketed MAX. When Bucket Size is set, specify a single property name (e.g. `resource_id`) to compute MAX per group within each time bucket, then sum (per bucket and across buckets) 4. **Usage Settings** * **Usage Reset**: Periodic (for peak tracking per period) * **Unit Name**: `user / users` 5. **Save Feature** ## MAX - Modes MAX aggregation can operate in two modes depending on whether `bucket_size` is specified. **`group_by` is only applied when using bucketed MAX** (i.e. when `bucket_size` is set). ### Mode 1: Standard MAX (Non-Bucketed) **When:** `bucket_size` is NOT specified (group\_by is not used)\ **Returns:** Overall maximum value across all events\ **Use for:** Simple peak detection ### Mode 2: Bucketed MAX (Windowed) **When:** `bucket_size` IS specified\ **Returns:** Sum of maximum values from each time bucket (see formula below)\ **Use for:** Cumulative peak billing, tiered capacity models **Optional: Bucketed MAX with group\_by**\ When **Group By** is also set (a single property name string), within **each time bucket** events are grouped by that property. For each bucket: MAX is computed per group, then those group maximums are summed. The final result is the sum of these per-bucket totals. Use this for per-resource or per-entity peak billing within time windows (e.g. per resource, per project, per region). **group\_by has no effect without bucket\_size.** ## Calculation Examples ### Standard MAX Example #### Event Data ```json theme={null} [ { "event_id": "evt_001", "event_name": "concurrent.users", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:00:00Z", "properties": { "user_count": 25 } }, { "event_id": "evt_002", "event_name": "concurrent.users", "external_customer_id": "customer_123", "timestamp": "2024-01-15T11:30:00Z", "properties": { "user_count": 40 } }, { "event_id": "evt_003", "event_name": "concurrent.users", "external_customer_id": "customer_123", "timestamp": "2024-01-15T14:00:00Z", "properties": { "user_count": 35 } } ] ``` #### Standard MAX Calculation **Process:** Find the highest value across all events\ **Result:** `40 users` (maximum from all events) *** ### Bucketed MAX Example #### Configuration * **Bucket Size:** HOUR * **Slab Pricing:** * **0-5 GB:** 0 Rs (free tier) * **5-10 GB:** 2 Rs per GB * **10+ GB:** 3 Rs per GB #### Event Data ```json theme={null} [ { "event_id": "evt_001", "event_name": "storage.usage", "external_customer_id": "customer_123", "timestamp": "2024-01-15T07:30:00Z", "properties": { "gb_used": 8 } }, { "event_id": "evt_002", "event_name": "storage.usage", "external_customer_id": "customer_123", "timestamp": "2024-01-15T07:45:00Z", "properties": { "gb_used": 4 } }, { "event_id": "evt_003", "event_name": "storage.usage", "external_customer_id": "customer_123", "timestamp": "2024-01-15T08:15:00Z", "properties": { "gb_used": 10 } }, { "event_id": "evt_004", "event_name": "storage.usage", "external_customer_id": "customer_123", "timestamp": "2024-01-15T08:30:00Z", "properties": { "gb_used": 5 } }, { "event_id": "evt_005", "event_name": "storage.usage", "external_customer_id": "customer_123", "timestamp": "2024-01-15T08:45:00Z", "properties": { "gb_used": 9 } } ] ``` ### Bucketed MAX Formula ``` total_result = MAX(bucket_1) + MAX(bucket_2) + ... + MAX(bucket_n) ``` Where each bucket represents a time window defined by `bucket_size`. #### Calculation Process **Hour 1 (7:00-8:00 UTC):** * Events: 8 GB, 4 GB * **Bucket Maximum:** 8 GB **Hour 2 (8:00-9:00 UTC):** * Events: 10 GB, 5 GB, 9 GB * **Bucket Maximum:** 10 GB **Total Bucketed MAX:** 8 GB + 10 GB = **18 GB** #### Tiered Billing Calculation **Billing Calculation for 18 GB total:** * First 5 GB: 0 Rs * Next 5 GB (5-10): 5 × 2 = 10 Rs * Remaining 8 GB (10-18): 8 × 3 = 24 Rs * **Total: 34 Rs** *** ### Bucketed MAX with group\_by Example **Group By** is only available with bucketed MAX. When set, within **each time bucket** events are grouped by the chosen property; MAX is computed per group in that bucket, then those group maximums are summed. The final result is the sum of these per-bucket totals. Useful for per-resource or per-entity peak billing within time windows. #### Configuration * **Aggregation Field:** `data` * **Bucket Size:** HOUR (required for group\_by to apply) * **Group By:** `resource_id` (single property name; must exist on event properties) MAX feature with Bucket Size and Group By configured *Screenshot: Metered feature with Aggregation Function = Max, Aggregation Field = data, Bucket Size = HOUR, and Group By = resource\_id.* #### Event Data ```json theme={null} [ { "event_id": "evt_A", "event_name": "resource.usage", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:00:00Z", "properties": { "data": 10, "resource_id": "resource_a" } }, { "event_id": "evt_B", "event_name": "resource.usage", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:30:00Z", "properties": { "data": 20, "resource_id": "resource_b" } }, { "event_id": "evt_C", "event_name": "resource.usage", "external_customer_id": "customer_123", "timestamp": "2024-01-15T11:15:00Z", "properties": { "data": 15, "resource_id": "resource_a" } } ] ``` #### Bucketed MAX with group\_by Formula ``` total_result = sum over buckets of ( MAX(group_1 in bucket) + MAX(group_2 in bucket) + ... + MAX(group_n in bucket) ) ``` Within each time bucket, events are grouped by the `group_by` property; each group’s maximum is taken, then summed. Those per-bucket sums are then added together. #### Calculation Process **Hour 1 (10:00–11:00 UTC):** * **resource\_a:** 10 → MAX = 10 * **resource\_b:** 20 → MAX = 20 * **Bucket 1 total:** 10 + 20 = **30** **Hour 2 (11:00–12:00 UTC):** * **resource\_a:** 15 → MAX = 15 * **Bucket 2 total:** **15** **Total (Bucketed MAX with group\_by):** 30 + 15 = **45** Without `group_by`, bucketed MAX would give 20 + 15 = 35 (max per hour). With `group_by: "resource_id"`, within each hour we sum the max per resource, then sum across hours: **45**. ## Use Cases ### Standard MAX Use Cases #### Peak Concurrent Users **Perfect for**: Maximum simultaneous users, peak connections ```json theme={null} { "event_name": "concurrent.users", "external_customer_id": "acme_corp", "properties": { "active_users": 150 } } ``` #### Peak Resource Usage **Perfect for**: Maximum CPU, peak memory, highest bandwidth ```json theme={null} { "event_name": "resource.peak", "external_customer_id": "user_456", "properties": { "cpu_percent": 85 } } ``` ### Bucketed MAX Use Cases #### Cumulative Peak Billing **Perfect for**: Pay-per-peak hour models, capacity-based pricing ```json theme={null} { "event_name": "capacity.usage", "external_customer_id": "merchant_789", "properties": { "gb_capacity": 50 } } ``` #### Infrastructure Peak Tracking **Perfect for**: Peak detection across time windows for billing ```json theme={null} { "event_name": "server.load", "external_customer_id": "customer_101", "properties": { "peak_instances": 12 } } ``` ### Bucketed MAX with group\_by Use Cases *Requires Bucket Size to be set; group\_by is only applied with bucketed MAX.* #### Per-Resource or Per-Entity Peak Billing **Perfect for**: Billing on the sum of peak usage per resource within each time window (e.g. per resource ID, per project ID, per region) ```json theme={null} { "event_name": "resource.usage", "external_customer_id": "customer_123", "properties": { "data": 50, "resource_id": "res_abc" } } ``` #### Multi-Tenant or Multi-Project Peak **Perfect for**: Sum of each tenant’s or project’s maximum usage ```json theme={null} { "event_name": "capacity.usage", "external_customer_id": "platform_customer", "properties": { "gb_used": 100, "project_id": "proj_xyz" } } ``` ### When to Use Each Mode ✅ **Use Standard MAX when:** * Need simple peak detection * Billing based on overall maximum * Single highest value matters ✅ **Use Bucketed MAX when:** * Cumulative peak billing models * Time-series peak analysis * Tiered capacity pricing * Sum of peaks across time windows ✅ **Use Bucketed MAX with group\_by when:** * You already use bucketed MAX (Bucket Size is set) and want per-resource or per-entity breakdown * Billing per resource, per project, or per entity **within each time bucket** * `group_by` is a single property name string on your events * **Note:** group\_by is only applied when bucket\_size is set ### Key Differences | Aspect | Standard MAX | Bucketed MAX | Bucketed MAX + group\_by | | ----------------- | ---------------------- | ------------------------ | ---------------------------------------------------------- | | **Configuration** | No bucket\_size | Requires bucket\_size | Requires bucket\_size + group\_by (single field) | | **Calculation** | Single maximum value | Sum of bucket maximums | Per bucket: sum of group maximums; then sum across buckets | | **Use Case** | Overall peak detection | Cumulative peak billing | Per-resource / per-entity peak within time windows | | **Result** | Simple max value | Sum of time-window peaks | Sum of (per-bucket sum of each group’s max) | ### ⚠️ Critical Notes * **bucket\_size can ONLY be used with MAX aggregation** - No other aggregation supports this * **Bucketed MAX sums the maximums** - Does NOT return max of maximums * **group\_by is only applied with bucketed MAX** - You must set Bucket Size for group\_by to take effect. Within each time bucket, events are grouped by the `group_by` property; MAX is computed per group, then those group maximums are summed (per bucket), and bucket results are summed ## Next Steps * **[Creating a Metered Feature](/docs/event-ingestion/creating-a-metered-feature)** - Complete setup guide * **[Sending Events](/docs/event-ingestion/sending-events)** - How to transmit MAX events # Overview Source: https://docs.flexprice.io/docs/product-catalogue/features/aggregation/overview **Aggregation types** define how usage is measured from incoming events. They are used to **calculate billable consumption** and form the foundation of usage-based billing. **Flexprice** supports the following **aggregation types:** | Aggregation | Description | Transcription | | ----------------------- | -------------------------------------------------------- | --------------------------------------------------- | | **COUNT** | Counts event occurrences for basic usage tracking | COUNT(DISTINCT events.id) | | **SUM** | Aggregates numeric property values across events | SUM(events.properties.property\_name) | | **AVERAGE** | Computes mean values from event properties | AVG(events.properties.property\_name) | | **COUNT UNIQUE** | Tracks distinct property values for unique metrics | COUNT\_DISTINCT(events.properties.property\_name) | | **LATEST** | Captures the most recent property value by timestamp | argMax(events.properties.property\_name, timestamp) | | **SUM WITH MULTIPLIER** | Applies configurable rate multipliers to summed values | SUM(events.properties.property\_name) \* multiplier | | **MAX** | Identifies peak values with optional bucketing support | MAX(events.properties.property\_name) | | **WEIGHTED SUM** | Time-proportional aggregation for capacity-based billing | Weighted calculation based on event duration | All aggregation types except **COUNT** operate on event properties. **The result of this aggregation will be used to calculate charges.** ## Next Steps * [COUNT Aggregation](/docs/product-catalogue/features/aggregation/count) * [SUM Aggregation](/docs/product-catalogue/features/aggregation/sum) * [AVERAGE Aggregation](/docs/product-catalogue/features/aggregation/average) * [COUNT UNIQUE Aggregation](/docs/product-catalogue/features/aggregation/count-unique) * [LATEST Aggregation](/docs/product-catalogue/features/aggregation/latest) * [SUM WITH MULTIPLIER Aggregation](/docs/product-catalogue/features/aggregation/sum-with-multiplier) * [MAX Aggregation](/docs/product-catalogue/features/aggregation/max) * [WEIGHTED SUM Aggregation](/docs/product-catalogue/features/aggregation/weighted-sum) # SUM Source: https://docs.flexprice.io/docs/product-catalogue/features/aggregation/sum Sums the values of a specified property across all matching events. Step-by-Step Setup when creating a SUM-based metered feature: 1. **Navigate to Features** * Go to Product Catalog → Features * Click "Add Feature" 2. **Basic Information** * **Name**: "Data Transfer" (or descriptive name) * **Type**: Select "Metered" 3. **Event Configuration** * **Event Name**: `data.transfer` (must match your event data) * **Aggregation Function**: Sum * **Aggregation Field**: `gb` (the property to sum) 4. **Usage Settings** * **Usage Reset**: Periodic (for monthly limits) or Cumulative * **Unit Name**: `GB / GBs` 5. **Save Feature** ## Calculation Example ### Event Data ```json theme={null} [ { "event_id": "evt_001", "event_name": "data.transfer", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:00:00Z", "properties": { "gb": 5.2 } }, { "event_id": "evt_002", "event_name": "data.transfer", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:05:00Z", "properties": { "gb": 3.8 } }, { "event_id": "evt_001", // Duplicate event ID "event_name": "data.transfer", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:10:00Z", "properties": { "gb": 7.1 } } ] ``` ### Calculation Process 1. **Event Matching**: All events with `event_name = "data.transfer"` 2. **Deduplication**: Remove duplicate event IDs, keeping latest value * `evt_001` → `7.1 GB` (latest value for this ID) * `evt_002` → `3.8 GB` 3. **Sum Result**: `7.1 + 3.8 = 10.9 GB` **Result**: `10.9 GBs` ## Use Cases ### Data Transfer Tracking **Perfect for**: Bandwidth usage, file downloads, API data transfer ```json theme={null} { "event_name": "data.transfer", "external_customer_id": "acme_corp", "properties": { "gb": 2.5 } } ``` ### Compute Time **Perfect for**: CPU hours, GPU usage, processing time ```json theme={null} { "event_name": "compute.usage", "external_customer_id": "user_456", "properties": { "hours": 1.5 } } ``` ### Storage Usage **Perfect for**: Disk space, database storage, file storage ```json theme={null} { "event_name": "storage.usage", "external_customer_id": "merchant_789", "properties": { "mb": 512 } } ``` ### Credits Consumed **Perfect for**: AI model usage, API credits, processing credits ```json theme={null} { "event_name": "model.usage", "external_customer_id": "customer_101", "properties": { "credits": 25 } } ``` ### When to Use SUM ✅ **Use SUM when:** * Tracking quantities or volumes * Measuring resource consumption * Accumulating usage values * Need precise numeric totals ## Next Steps * **[Creating a Metered Feature](/docs/event-ingestion/creating-a-metered-feature)** - Complete setup guide * **[Sending Events](/docs/event-ingestion/sending-events)** - How to transmit SUM events # SUM WITH MULTIPLIER Source: https://docs.flexprice.io/docs/product-catalogue/features/aggregation/sum-with-multiplier Sums property values and applies a configurable multiplier. Step-by-Step Setup when creating a SUM WITH MULTIPLIER-based metered feature: 1. **Navigate to Features** * Go to Product Catalog → Features * Click "Add Feature" 2. **Basic Information** * **Name**: "API Credits (USD)" (or descriptive name) * **Type**: Select "Metered" 3. **Event Configuration** * **Event Name**: `api.usage` (must match your event data) * **Aggregation Function**: Sum with Multiplier * **Aggregation Field**: `credits` (the property to sum) * **Multiplier**: `0.001` (rate to apply - e.g., \$0.001 per credit) 4. **Usage Settings** * **Usage Reset**: Periodic (for monthly billing) or Cumulative * **Unit Name**: `USD` (currency or unit after multiplier) 5. **Save Feature** ## Calculation Example ### Event Data ```json theme={null} [ { "event_id": "evt_001", "event_name": "api.usage", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:00:00Z", "properties": { "credits": 1000 } }, { "event_id": "evt_002", "event_name": "api.usage", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:05:00Z", "properties": { "credits": 2500 } }, { "event_id": "evt_003", "event_name": "api.usage", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:10:00Z", "properties": { "credits": 1500 } }, { "event_id": "evt_001", // Duplicate event ID "event_name": "api.usage", "external_customer_id": "customer_123", "timestamp": "2024-01-15T10:15:00Z", "properties": { "credits": 800 } } ] ``` ### Configuration * **Multiplier**: `0.001` (converts credits to USD at \$0.001 per credit) ### Sum with Multiplier Formula ``` final_result = SUM(values) × multiplier ``` ### Calculation Process 1. **Event Matching**: All events with `event_name = "api.usage"` 2. **Deduplication**: Remove duplicate event IDs, keeping latest value * `evt_001` → `800 credits` (latest value for this ID) * `evt_002` → `2500 credits` * `evt_003` → `1500 credits` 3. **Sum Calculation**: `800 + 2500 + 1500 = 4800 credits` 4. **Apply Multiplier**: `4800 × 0.001 = $4.80` **Result**: `$4.80 USD` ## Use Cases ### Currency Conversion **Perfect for**: Converting between currencies, unit price calculations ```json theme={null} { "event_name": "sales.transaction", "external_customer_id": "acme_corp", "properties": { "amount_eur": 100 } } ``` *Multiplier: `1.10` (EUR to USD conversion)* ### Credit-Based Pricing **Perfect for**: API credits, processing credits, usage tokens ```json theme={null} { "event_name": "api.credits", "external_customer_id": "user_456", "properties": { "credits_used": 250 } } ``` *Multiplier: `0.01` (converts credits to dollars)* ### Tiered Rate Calculations **Perfect for**: Volume discounts, tier-based pricing, rate adjustments ```json theme={null} { "event_name": "data.processing", "external_customer_id": "merchant_789", "properties": { "gb_processed": 500 } } ``` *Multiplier: `0.05` (premium tier rate: \$0.05/GB)* ### Unit Conversions **Perfect for**: Converting units, standardizing measurements ```json theme={null} { "event_name": "storage.usage", "external_customer_id": "customer_101", "properties": { "mb_stored": 1024 } } ``` *Multiplier: `0.001` (converts MB to GB)* ### Commission Calculations **Perfect for**: Revenue sharing, affiliate commissions, partner payouts ```json theme={null} { "event_name": "transaction.revenue", "external_customer_id": "partner_user", "properties": { "gross_revenue": 1000 } } ``` *Multiplier: `0.15` (15% commission rate)* ### When to Use SUM WITH MULTIPLIER ✅ **Use SUM WITH MULTIPLIER when:** * Need to apply consistent rate conversions * Converting between units or currencies * Implementing tiered or variable pricing * Calculating commissions or percentage-based fees * Standardizing different measurement units ### Configuration Requirements ⚠️ **Critical Notes:** * **Multiplier must be > 0** - Cannot use negative or zero values * **Multiplier is immutable** - Cannot be changed after feature creation * **High precision** - Stored as decimal for accurate calculations * **Applied after summing** - Sum first, then multiply (not per-event) ## Next Steps * **[Creating a Metered Feature](/docs/event-ingestion/creating-a-metered-feature)** - Complete setup guide * **[Sending Events](/docs/event-ingestion/sending-events)** - How to transmit SUM WITH MULTIPLIER events # WEIGHTED SUM Source: https://docs.flexprice.io/docs/product-catalogue/features/aggregation/weighted-sum Time-weighted sum calculation where values are prorated based on duration. Step-by-Step Setup when creating a WEIGHTED SUM-based metered feature: 1. **Navigate to Features** * Go to Product Catalog → Features * Click "Add Feature" 2. **Basic Information** * **Name**: "Reserved Storage" (or descriptive name) * **Type**: Select "Metered" 3. **Event Configuration** * **Event Name**: `storage.reserved` (must match your event data) * **Aggregation Function**: Weighted Sum * **Aggregation Field**: `gb_reserved` (the property to weight by time) 4. **Usage Settings** * **Usage Reset**: Periodic (for capacity-based billing) * **Unit Name**: `GB-time` (time-weighted unit) 5. **Save Feature** ## Calculation Example ### Billing Period **Period:** July 31, 2025 18:30:00 UTC to August 31, 2025 18:30:00 UTC\ **Total period duration:** 31 days = 2,678,400 seconds ### Event Data ```json theme={null} [ { "event_id": "evt_001", "event_name": "storage.reserved", "external_customer_id": "customer_123", "timestamp": "2025-08-16T00:00:00Z", "properties": { "gb_reserved": 20 } }, { "event_id": "evt_002", "event_name": "storage.reserved", "external_customer_id": "customer_123", "timestamp": "2025-08-18T00:00:00Z", "properties": { "gb_reserved": 10 } }, { "event_id": "evt_003", "event_name": "storage.reserved", "external_customer_id": "customer_123", "timestamp": "2025-08-20T00:00:00Z", "properties": { "gb_reserved": 10 } }, { "event_id": "evt_004", "event_name": "storage.reserved", "external_customer_id": "customer_123", "timestamp": "2025-08-25T00:00:00Z", "properties": { "gb_reserved": 5 } } ] ``` ### Weighted Sum Formula ``` weighted_value = (property_value / total_period_seconds) × seconds_from_event_to_period_end ``` ### Calculation Process #### Event 1: August 16, 2025 - 20 GB * **Event timestamp:** August 16, 2025 00:00:00 UTC * **Seconds until period end:** August 31 18:30:00 - August 16 00:00:00 = 15 days, 18.5 hours = 1,365,300 seconds * **Weighted contribution:** (20 / 2,678,400) × 1,365,300 = (20 × 1,365,300) / 2,678,400 = 27,306,000 / 2,678,400 = **10.198660714285714 GB** #### Event 2: August 18, 2025 - 10 GB * **Event timestamp:** August 18, 2025 00:00:00 UTC * **Seconds until period end:** August 31 18:30:00 - August 18 00:00:00 = 13 days, 18.5 hours = 1,197,300 seconds * **Weighted contribution:** (10 / 2,678,400) × 1,197,300 = (10 × 1,197,300) / 2,678,400 = 11,973,000 / 2,678,400 = **4.470982142857143 GB** #### Event 3: August 20, 2025 - 10 GB * **Event timestamp:** August 20, 2025 00:00:00 UTC * **Seconds until period end:** August 31 18:30:00 - August 20 00:00:00 = 11 days, 18.5 hours = 1,029,300 seconds * **Weighted contribution:** (10 / 2,678,400) × 1,029,300 = (10 × 1,029,300) / 2,678,400 = 10,293,000 / 2,678,400 = **3.843303571428571 GB** #### Event 4: August 25, 2025 - 5 GB * **Event timestamp:** August 25, 2025 00:00:00 UTC * **Seconds until period end:** August 31 18:30:00 - August 25 00:00:00 = 6 days, 18.5 hours = 573,300 seconds * **Weighted contribution:** (5 / 2,678,400) × 573,300 = (5 × 573,300) / 2,678,400 = 2,866,500 / 2,678,400 = **1.070200892857143 GB** ### Total Weighted Sum **10.198660714285714 + 4.470982142857143 + 3.843303571428571 + 1.070200892857143 = 19.583147321428571 GB** **Result**: `19.583147321428571 GB-time` ## Use Cases ### Reserved Capacity Billing **Perfect for**: Cloud storage reservations, compute capacity, bandwidth reservations ```json theme={null} { "event_name": "capacity.reserved", "external_customer_id": "acme_corp", "properties": { "cpu_cores": 8 } } ``` ### Time-Based Resource Usage **Perfect for**: Database connections, server instances, license usage ```json theme={null} { "event_name": "database.connection", "external_customer_id": "user_456", "properties": { "connection_count": 5 } } ``` ### Subscription Changes **Perfect for**: Mid-cycle plan changes, prorated upgrades/downgrades ```json theme={null} { "event_name": "plan.upgrade", "external_customer_id": "merchant_789", "properties": { "new_tier_level": 3 } } ``` ### Infrastructure Provisioning **Perfect for**: VM instances, container resources, network bandwidth ```json theme={null} { "event_name": "vm.provisioned", "external_customer_id": "customer_101", "properties": { "memory_gb": 16 } } ``` ### License Allocation **Perfect for**: Software seats, user licenses, feature entitlements ```json theme={null} { "event_name": "license.allocated", "external_customer_id": "enterprise_user", "properties": { "seat_count": 25 } } ``` ### When to Use WEIGHTED SUM ✅ **Use WEIGHTED SUM when:** * Billing for reserved or allocated capacity * Need time-proportional calculations * Handling mid-cycle subscription changes * Measuring sustained resource usage over time * Events earlier in the period have more time remaining and get higher weight ### Key Characteristics * **Time-weighted calculation** - Events earlier in the period have higher impact * **Perfect for capacity billing** - Bills based on how long resources were allocated * **Handles subscription changes** - Automatically prorates usage changes * **Complex formula** - More computationally intensive than other aggregations ## Next Steps * **[Creating a Metered Feature](/docs/event-ingestion/creating-a-metered-feature)** - Complete setup guide * **[Sending Events](/docs/event-ingestion/sending-events)** - How to transmit WEIGHTED SUM events # Creating a feature Source: https://docs.flexprice.io/docs/product-catalogue/features/create Follow the steps below to create a feature in Flexprice: 1. In the main navigation menu, select **Features** from **Product Catalog**. 2. At the top-right of the Feature dashboard, select **Add Feature** to create a new feature. 3. Fill in the feature details: * **Feature Name:** Enter a unique and descriptive name for the feature (e.g., "Premium API Access"). * **Feature Type:** Select the type of feature: * **Metered:** Tracks quantifiable usage (e.g., number of API calls) * **Boolean:** Indicates a simple on/off state (e.g., access to a premium dashboard) * **Static:** Represents fixed attributes or entitlements (e.g., priority support) * **Config:** Delivers a custom JSON configuration to your application through the customer's entitlements (e.g., feature flags, rate limits, tenant settings) ## How to create a Boolean feature Select **Boolean** as Type which indicates a simple on/off state (e.g., access to a premium dashboard). Boolean Feature Creation ## How to create a Static feature Select **Static** as feature type which represents fixed attributes or entitlements (e.g., priority support) Static Feature Creation ## How to create a Config feature A Config feature lets you attach a JSON object to an entitlement as a `config_value`. Flexprice stores and delivers this payload through plans and subscriptions, giving your application full control over how the configuration is applied. In the **Add Feature** form, choose **Config** as the Feature Type. The `config_value` is set at the entitlement level, not on the feature itself. You define it when attaching this feature to a plan. Navigate to the target plan, open the **Entitlements** tab, and select **Add Entitlement**. Choose the Config feature you created. A JSON editor appears. Enter a valid JSON object as the `config_value`. The editor validates the input and blocks submission if the value is not valid JSON. Add Entitlement with Config feature JSON editor Click **Add** to save the entitlement. The plan's Entitlements tab displays a truncated JSON preview in the **Value** column. Click the preview to open the full stored config payload. Entitlements tab showing Config feature value The `config_value` is available on the customer's entitlements as soon as they subscribe to a plan that includes this feature. Use the [Get Customer Entitlements](/api-reference/customers/get-customer-entitlements) endpoint to retrieve it. ## How to create a Metered feature 1. Select **Metered** as Type which tracks quantifiable usage (e.g., number of API calls). 2. Optionally edit the singular and plural unit names, for example: "token" and "tokens". Metered Feature Creation ### Define Event Details Event Details #### Event Name For example, suppose you want to track how many tokens users consume while calling your AI model: * **Event Name**: `tokens_total` * This is the unique identifier you'll use to send events that match the metric * Must be unique across your Flexprice account to avoid confusion with other metrics #### Filters (Optional) It is important to note that filters cannot be edited later, so decide up front if you'll need them for billing or reporting. Filters let you **narrow down** which events get counted or summed in this metric. You do not have to add filters if you want to aggregate all events of a given type. However, filters become crucial if you need more granular control, such as billing for only certain AI models or distinguishing usage by environment. **Example**: If your system supports multiple AI models (`gpt 3`, `llama3.2`, `gpt 4`), and you only want to track usage from GPT models for specialized billing, you can add a filter: * **Key**: `model` * **Values**: `gpt 3`, `gpt 4` This means Flexprice will only aggregate token usage events where `model` is either `gpt 3` or `gpt 4`. ### Define Aggregation The aggregation settings tell Flexprice how to measure your events. Aggregation Settings #### Function Choose one of these aggregation functions (see [Aggregation Overview](/docs/product-catalogue/features/aggregation/overview) for detailed explanations): * **COUNT**: Counts the total number of matching events, regardless of any numeric value in the event data * **SUM**: Adds a numeric property (like tokens, price, etc.) across all matching events * **AVERAGE**: Calculates the mean value of a specified property across all matching events * **COUNT UNIQUE**: Counts only the unique values of a specified event property * **LATEST**: Returns the most recent value of a specified property based on event timestamp * **SUM WITH MULTIPLIER**: Sums a property and applies a configurable multiplier for rate conversions * **MAX**: Returns the maximum value of a specified property (supports bucketed calculations) * **WEIGHTED SUM**: Time-weighted sum for capacity-based billing where values are prorated by duration #### Field * The property that Flexprice aggregates. Required for all aggregation functions except `COUNT` * For numeric aggregations (`SUM`, `AVERAGE`, `SUM WITH MULTIPLIER`, `MAX`, `WEIGHTED SUM`): specify a numeric property (e.g., `tokens`, `gb`, `response_time`) * For `COUNT UNIQUE`: specify any property whose distinct values you want to count (e.g., `user_id`, `session_id`) * For `LATEST`: specify the property whose most recent value you want to track (e.g., `tier_level`, `status`) * For `COUNT`: no field required (counts event occurrences only) #### Usage Reset * **Cumulative**: The meter never resets. Usage keeps accumulating across billing cycles, providing a running total. This is useful for features like storage. * **Periodic**: Usage resets at each billing period (e.g., monthly). This is ideal for most subscription-based models where usage resets for every billing period **Example Configuration**: * **Aggregation** = `SUM` * **Aggregation Value** = `tokens` * **Aggregation Type** = `Periodic` (if you invoice monthly and want usage to reset each month) # Custom Expression Source: https://docs.flexprice.io/docs/product-catalogue/features/custom-expression Compute a per-event quantity from event properties using a CEL formula, then aggregate the result. Custom Expression lets you write a small formula that runs on every event to compute the quantity that gets aggregated. Instead of aggregating a single property field directly, the aggregation runs on the result of the expression. Use it when the billable quantity is derived from multiple properties (e.g. `input_tokens + output_tokens`), needs a unit conversion (e.g. `duration_ms / 1000`), or needs to be rounded, clamped, or floored per event. Expressions are written in **CEL** (Common Expression Language). They are validated when the feature is saved, so syntax and type errors surface up front rather than at ingestion time. **Benefits:** * **Multi-property quantities** — Combine several event fields into one billable number (`input_tokens + output_tokens`, `cores * hours`) * **Per-event transforms** — Round, floor, ceil, or clamp before aggregation (`ceil(duration_ms / 1000)`, `min(usage, quota)`) * **Unit conversions** — Convert between units per event (`bytes / 1024`, `duration_ms / 1000`) * **Validated at save time** — Syntax errors, missing operators, and non-numeric expressions are rejected when the feature is created, not per event * **No code deploys** — Change the pricing formula from the dashboard when your metering model evolves Custom Expression is only available for **SUM**, **AVG**, **MAX**, and **LATEST** aggregations. It is not supported for COUNT, COUNT UNIQUE, SUM WITH MULTIPLIER, or WEIGHTED SUM. ## How It Works For each event, Flexprice reads the top-level properties in `event.properties`, evaluates your expression against them, and treats the result as that event's quantity. The chosen aggregation (SUM / AVG / MAX / LATEST) then runs over those per-event quantities. ``` per_event_quantity = evaluate(expression, event.properties) feature_value = aggregate(per_event_quantity across events) ``` * **Variables** are top-level property names on your events (e.g. `tokens`, `duration_ms`, `input_tokens`). Nested paths are not supported — flatten the value to a top-level property. * **Missing properties** are treated as `0`. * **Numeric strings** (e.g. `"2"`, `"5.5"`) are coerced to numbers so events don't fail just because a field was serialized as a string. * **Non-numeric strings**, `NaN`, and `±Inf` are rejected — the event's quantity fails to compute and the error is surfaced. * **All arithmetic is real-valued.** Integer literals are promoted to doubles, so `total / 4` with `total = 10` evaluates to `2.5`, not `2`. ## Step-by-Step Setup Go to **Product Catalog → Features** and click **Add Feature**. * **Name**: descriptive name (e.g. `Phone Call Seconds`) * **Type**: Select **Metered** * **Event Name**: the event you send (e.g. `phone_call`) * **Aggregation Function**: pick **Sum**, **Average**, **Max**, or **Latest** * **Aggregation Field**: the property that would be used if no expression is set (e.g. `tokens`). This is still required as a fallback / label. Click **+ Custom expression** below the aggregation section. Aggregation section with the Custom expression toggle highlighted Enter a CEL formula in the **Custom Expression** field. Variables are the top-level property names on your events. Custom Expression field with ceil(duration_ms / 1000) and inline help text listing available functions On save, the expression is compiled and validated. If it has a syntax error, references no variables, or evaluates to a non-numeric type, saving fails with the error. ## Supported Functions The following math helpers are available inside expressions. All take and return numbers. | Function | Signature | Description | | ----------- | --------------------------- | ------------------------------------ | | `max(a, b)` | `(number, number) → number` | Returns the larger of `a` and `b` | | `min(a, b)` | `(number, number) → number` | Returns the smaller of `a` and `b` | | `pow(x, y)` | `(number, number) → number` | Raises `x` to the power `y` | | `abs(x)` | `(number) → number` | Absolute value | | `ceil(x)` | `(number) → number` | Rounds up to the nearest integer | | `floor(x)` | `(number) → number` | Rounds down to the nearest integer | | `round(x)` | `(number) → number` | Rounds half away from zero | | `sqrt(x)` | `(number) → number` | Square root; errors if `x < 0` | | `log(x)` | `(number) → number` | Natural logarithm; errors if `x ≤ 0` | Standard CEL operators are also available: `+`, `-`, `*`, `/`, `<`, `<=`, `>`, `>=`, `==`, `!=`, `&&`, `||`, `!`, and the ternary `cond ? a : b`. Modulo (`%`) is not available because all values are typed as doubles. Use `x - floor(x / y) * y` if you need a remainder. ## Calculation Example ### Configuration * **Event Name**: `phone_call` * **Aggregation Function**: Sum * **Custom Expression**: `ceil(duration_ms / 1000)` * **Unit Name**: `second / seconds` ### Event Data ```json theme={null} [ { "event_id": "evt_001", "event_name": "phone_call", "external_customer_id": "customer_123", "timestamp": "2026-07-09T10:00:00Z", "properties": { "duration_ms": 4200 } }, { "event_id": "evt_002", "event_name": "phone_call", "external_customer_id": "customer_123", "timestamp": "2026-07-09T10:05:00Z", "properties": { "duration_ms": 8100 } }, { "event_id": "evt_003", "event_name": "phone_call", "external_customer_id": "customer_123", "timestamp": "2026-07-09T10:10:00Z", "properties": { "duration_ms": 500 } } ] ``` ### Calculation Process 1. **Per-event quantity** — Evaluate `ceil(duration_ms / 1000)` on each event * `evt_001` → `ceil(4200 / 1000)` = `5` * `evt_002` → `ceil(8100 / 1000)` = `9` * `evt_003` → `ceil(500 / 1000)` = `1` 2. **Aggregation** — SUM across events: `5 + 9 + 1 = 15` **Result**: `15 seconds` Without the expression, a plain `SUM(duration_ms)` would have produced `12,800` — the expression bills whole seconds with per-call rounding, which is what the pricing model requires. ## Use Cases ### Token-based AI billing **Perfect for**: LLM APIs that charge different rates for input vs. output tokens. **Expression**: `input_tokens + output_tokens * 3` ```json theme={null} { "event_name": "llm.completion", "external_customer_id": "acme_corp", "properties": { "input_tokens": 500, "output_tokens": 120, "model": "gpt-4" } } ``` *Aggregation: SUM. Each output token is billed as 3 units to reflect the higher output cost.* ### Rounded call duration (telephony) **Perfect for**: Voice APIs billed by the whole second or whole minute, with per-call rounding. **Expression**: `ceil(duration_ms / 1000)` ```json theme={null} { "event_name": "phone_call", "external_customer_id": "customer_123", "properties": { "duration_ms": 8100 } } ``` *Aggregation: SUM. A 8.1s call is billed as 9 seconds; the sum reflects total billable seconds.* ### Compute cost per event **Perfect for**: Infrastructure billing where quantity depends on multiple dimensions per job. **Expression**: `cores * hours * rate` ```json theme={null} { "event_name": "compute.job", "external_customer_id": "user_456", "properties": { "cores": 8, "hours": 2.5, "rate": 0.05 } } ``` *Aggregation: SUM. Each job contributes its own cost; the feature value is total spend.* ### Quota-clamped usage **Perfect for**: Fair-use plans that cap what a single event can contribute. **Expression**: `min(api_calls, 1000)` ```json theme={null} { "event_name": "api.batch", "external_customer_id": "merchant_789", "properties": { "api_calls": 4200 } } ``` *Aggregation: SUM. A batch of 4,200 calls contributes at most 1,000 to the metered total.* ### Pulse pricing (minimum billable unit) **Perfect for**: Charging in fixed pulses (e.g. 15-second SMS pulses, 6-second call pulses). **Expression**: `duration <= 10 ? 0 : ceil(duration / 15) * 15` ```json theme={null} { "event_name": "sms.send", "external_customer_id": "customer_101", "properties": { "duration": 18 } } ``` *Aggregation: SUM. Short pulses under 10 units are free; longer pulses round up to the next 15-unit block.* ### Data transfer with unit conversion **Perfect for**: Events emitted in bytes but billed in GB. **Expression**: `bytes / 1073741824` ```json theme={null} { "event_name": "data.transfer", "external_customer_id": "acme_corp", "properties": { "bytes": 5368709120 } } ``` *Aggregation: SUM. Each event converts bytes to GB; the feature value is total GB transferred.* ## Error Handling Expressions can fail either at **save time** (compile) or **event time** (evaluate). Compile errors block feature creation; evaluate errors cause the specific event's quantity to fail. ### Compile-time errors Surfaced when you save the feature. Fix the expression before saving. | Error | Cause | Example | | ------------------ | ----------------------------------------- | ------------- | | Syntax error | Malformed CEL | `a + b +` | | No variables | Expression has only literals | `1 + 2` | | Non-numeric result | Result is not a number | `a + "hello"` | | Unknown function | Function is not in the supported list | `tan(theta)` | | Wrong arity | Function called with wrong number of args | `max(tokens)` | ### Event-time errors Surfaced per event during ingestion. | Error | Cause | | ----------------------- | ---------------------------------------------------------------------------------- | | Property is not numeric | An identifier resolves to a non-numeric string (e.g. `"abc"`) | | Non-finite property | Property value is `NaN`, `+Inf`, or `-Inf` | | Non-finite result | Expression produced `NaN` or `±Inf` (division by zero, `sqrt(-1)`, `pow` overflow) | Test your expression against a few real payloads in a scratch event before switching production traffic. A quick way is to enable the feature on a test plan and confirm the aggregated value matches what you expect. ## Critical Notes * **Only SUM, AVG, MAX, and LATEST** support custom expressions today. * **Top-level properties only** — nested fields (e.g. `payload.tokens`) are not addressable; flatten them at ingestion. * **Missing properties default to `0`**, not an error. If a required property is missing on the event, the expression will still evaluate but the quantity will reflect that zero. * **CEL reserved keywords** cannot be property names inside expressions: `as`, `break`, `const`, `continue`, `else`, `false`, `for`, `function`, `if`, `import`, `in`, `let`, `loop`, `package`, `namespace`, `null`, `return`, `true`, `var`, `void`, `while`. Rename the property on your event if it clashes. * **Real division** — Integer literals are promoted to doubles, so `total / 1000` returns a real number, not a truncated integer. * **Expression changes apply to new events only** — Historical events are not re-evaluated when you edit the expression. ## Next Steps * **[SUM Aggregation](/docs/product-catalogue/features/aggregation/sum)** — The most common aggregation used with custom expressions * **[Creating a Metered Feature](/docs/event-ingestion/creating-a-metered-feature)** — Full feature setup walkthrough * **[Sending Events](/docs/event-ingestion/sending-events)** — How to structure `event.properties` for expression variables # Link features to plans Source: https://docs.flexprice.io/docs/product-catalogue/features/linking-to-plans In **Flexprice**, managing customer access to features is done through **entitlements**. An **entitlement** defines which features a customer can access based on their subscription plan. This ensures that each customer receives the correct level of access, allowing precise control over feature availability and usage limits. **Linking Features to Plans through Entitlements** * Choose the plan to which you want to link features. * Navigate to the **Entitlements** section within the selected plan. * Click **Add** to add the desired features to the plan. Screenshot 2025-03-10 at 4.25.53 PM.png * Configure access parameters, including usage limits, permissions, and other relevant settings. Screenshot 2025-03-10 at 4.27.20 PM.png * Click **Save**, and the entitlements will be applied to all customers on this plan. **Configuring Entitlements for Different Feature Types** * **Boolean Features** * Selecting a Boolean feature **grants or restricts** access for customers on this plan. * No further configuration is required—just **Add** or **Cancel**. Screenshot 2025-03-10 at 4.27.59 PM.png * **Metered features** * **Setting the Value** Screenshot 2025-03-10 at 4.28.43 PM.png * Enter the maximum usage limit a customer is allowed before restrictions apply. * If there is no cap, select Set to Infinite. * **Usage reset** Screenshot 2025-03-10 at 4.29.43 PM.png * By default, metered features reset at the start of each billing period. * If needed, you can configure a separate reset interval, independent of the billing period. * When enabled, usage resets to zero at the specified interval, regardless of when the billing cycle occurs. 💡 **Example:** A plan is billed **annually**, but API call limits **reset every month**. Setting a **monthly reset** ensures that users get a fresh allocation of calls each month. * **Soft Limit vs. Hard Limit** Once a customer reaches their allocated limit, there are two ways to handle additional usage: * Soft Limit (Overage Allowed) * Customers can exceed their limit. * Excess usage may be charged separately (based on pricing rules). * Common for pay-as-you-go models. 💡 Example: A GPT API plan allows 50,000 tokens/month, but if exceeded, additional tokens are charged \$0.10 per 1,000 tokens. * Hard Limit (Strict Cap) * Customers cannot exceed the limit. * Once they reach their limit, requests are blocked. * Typically used for free tiers or strict allowances. * **Static Features** Screenshot 2025-03-10 at 4.30.12 PM.png * Instead of setting limits, define a custom value. * Example: 24/7 support, premium customer service, AI model access. * Customers will see these benefits listed as part of their subscription. The linked features are now associated with the plan, and subscribers will have access according to the configured entitlements. # Overview Source: https://docs.flexprice.io/docs/product-catalogue/features/overview A Feature in Flexprice represents a unit of functionality that customers can access, use, or be billed for. They form the foundational building blocks of your product offering. Features define what customers can do, how much they can use, and how they are charged—like API endpoints, storage limits, premium dashboards, priority support, or advanced analytics. **Types of features** Flexprice allows you to define three types of features: * **Metered Features** * **Boolean Features** * **Static Features** Screenshot 2025-03-08 at 7.19.10 PM.png **Boolean Features** Boolean Features represent capabilities or functionalities that customers either have access to or don't—essentially a simple on/off switch. For example, in a SaaS platform offering AI models shown above, a Boolean Feature like **SAML SSO Authentication** might be available only in higher-tier plans. In this case, customers on Plan 1 would not have access to SAML authentication, while those on Plan 2 would. **Metered Features** Metered Features track quantifiable usage by customers, allowing you to bill them based on how much they use. These features capture dynamic, variable usage over time. It forms the backbone for accurate invoicing, usage analysis, and flexible pricing plans. In the above example, a metered feature tracks the number of **GPT tokens** processed per month. A customer on Plan 1 may have a limit of 10,000 tokens per month, while a customer on Plan 2 could have 1,000,000 tokens per month. **Static Features** Static Features represent fixed attributes or configurations of your product that don't dynamically change based on usage. They are generally consistent and predefined per pricing plan or tier. Unlike Metered Features or Boolean Features, Static Features remain constant and serve as descriptors of a plan’s benefits. In this example, the Available Models is a static feature. # Use cases Source: https://docs.flexprice.io/docs/product-catalogue/features/use-cases Metered features in Flexprice allow you to **track usage-based metrics** like API requests, AI token consumption, and storage usage. Below are some **common real-world examples**, along with **why they are structured a certain way** and how to configure them in Flexprice. **AI Token Usage Tracking** In most AI applications, token usage needs to be tracked for billing and cost control. Since a single API interaction may consume multiple tokens (input, output, and system prompts), tracking token usage accurately is critical. Additionally, different LLM models (e.g., GPT-4, Claude, Llama) charge different rates per token, and token pricing varies based on input vs. output tokens. To capture and price these variations differently, we must store model type and prompt type as filters. ```jsx theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/meters \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "aggregation": { "field": "tokens", "type": "SUM" }, "event_name": "tokens_total", "name": "ai_tokens_used", "filters": [ { "key": "model_type", "values": [ "gpt-4" ] }, { "key": "prompt_type", "values": [ "input" ] } ], "reset_usage": "Periodic" }' ``` 💡 Outcome: Flexprice will sum up all tokens used within a billing cycle, while also tracking usage by model and prompt type. This allows you to bill differently for input vs. output tokens and adjust pricing per model. **GPU Compute Time Billing** Cloud infrastructure providers charge customers based on GPU time used for model training and inference. Since different GPUs (e.g., NVIDIA A100, H100) have different hourly costs, tracking GPU usage by type is necessary for accurate billing. Additionally, compute time is measured in seconds, but billing is often done in hourly increments. To ensure proper charge alignment, we track time in seconds and aggregate usage before billing. ```jsx theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/meters \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "aggregation": { "field": "time_seconds", "type": "SUM" }, "event_name": "gpu_time", "name": "gpu_time_used", "filters": [ { "key": "gpu_type", "values": [ "nvidia_a100" ] } ], "reset_usage": "Periodic" }' ``` 💡 Outcome: This setup enables per-second tracking of GPU usage, aggregated hourly for billing. Filtering by GPU type ensures each machine is billed at the correct rate. **API Request Counting** Many SaaS products with API-based pricing offer a fixed number of free requests per month, with overage charges for additional usage. For accurate billing: * Every API request should be counted. * Different endpoints may have different billing weights (e.g., a basic GET request vs. an expensive AI inference call). ```jsx theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/meters \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "aggregation": { "field": "", "type": "COUNT" }, "event_name": "api_calls", "name": "api_requests", "filters": [], "reset_usage": "Periodic" }' ``` 💡 Outcome: Every API call is counted and attributed to the customer, with overages billed at the correct rate. You can also filter by endpoint to charge differently for high-compute calls. # Feature-Wallet Balance Alert Source: https://docs.flexprice.io/docs/product-catalogue/features/wallet-balance-alert Set up automated alerts to monitor feature wallet balances and receive notifications when thresholds are crossed The Feature Wallet Balance Alert system enables real-time monitoring of wallet balances for specific features and triggers automated notifications when configured thresholds are breached. This powerful monitoring tool helps prevent service interruptions, manage usage limits, and track spending across your feature offerings. ## Overview Feature Wallet Balance Alerts provide a flexible, multi-level alerting system that monitors wallet balances associated with specific features. When a wallet balance crosses defined thresholds, the system automatically triggers alerts and sends webhook notifications, allowing you to take proactive action before critical issues arise. **Key Benefits:** * **Prevent Service Interruptions**: Get notified before wallet balance reaches critical levels * **Multi-Level Alerting**: Configure up to three independent alert levels (Critical, Warning, Info) * **Real-Time Monitoring**: Instant notifications via webhooks when thresholds are breached * **Flexible Configuration**: Set thresholds for "above" or "below" conditions based on your use case * **Automated Tracking**: No manual checking required—continuous background monitoring ## Alert Levels The system supports three independent alert levels, each with its own priority and purpose: ### 1. Critical (In Alarm) - Highest Priority **When to Use**: When immediate action is required to prevent service disruption or critical issues. **Example**: Balance reaches \$0 or less, meaning service may be interrupted within hours. ### 2. Warning - Medium Priority **When to Use**: When attention is needed soon, but there's still time to take action. **Example**: Balance reaches \$10 or less, indicating low balance that needs replenishment soon. ### 3. Info - Lowest Priority **When to Use**: For informational tracking and usage milestones. **Example**: Balance reaches \$20 or less, serving as an early tracking indicator. ### 4. OK - No Alert **Example**: Balance is healthy and above all configured thresholds. ## Alert States The system evaluates wallet balance against configured thresholds and determines the current alert state: * **OK**: Balance is healthy, no action needed * **Info**: Balance crossed info threshold, informational tracking only * **Warning**: Balance crossed warning threshold, attention needed * **In Alarm**: Balance crossed critical threshold, immediate action required ## How It Works ### Threshold Configuration Each threshold consists of two components: 1. **Threshold Value**: The numeric value to monitor (e.g., 100.00) 2. **Condition**: The direction that triggers an alert * **"below"**: Alert when balance is **less than or equal to** (≤) the threshold * **"above"**: Alert when balance is **greater than or equal to** (≥) the threshold **Important**: Alerts trigger on **equality**, not just when crossing the threshold. For example, if your critical threshold is set to \$0 with "below" condition, the alert triggers when balance is exactly \$0 **or** negative. ### Threshold Ordering Rules The system enforces proper ordering of thresholds based on the condition: **For "below" condition** (monitoring real-time ongoing-balance depletion): ``` Critical < Warning < Info ``` Example: Critical: \$0, Warning: \$10, Info: \$20 **For "above" condition** (monitoring usage/spending): ``` Critical > Warning > Info ``` Example: Critical: \$1000, Warning: \$500, Info: \$100 ### Configuration Flexibility The system supports multiple configuration patterns: * **Critical Only**: Critical threshold without warning or info * **Critical + Warning**: Critical and warning thresholds without info * **Critical + Info**: Critical and info thresholds without warning * **Info Only**: Standalone info threshold without critical or warning * **Full Stack**: All three thresholds (critical, warning, and info) Warning threshold **requires** a critical threshold. You cannot configure a warning threshold without also configuring a critical threshold. ## Real-World Use Cases ### Use Case 1: Prepaid Wallet Balance Monitoring **Scenario**: Track prepaid wallet balance to prevent service interruption **Configuration**: ```json theme={null} { "critical": { "threshold": "0.00", "condition": "below" }, "warning": { "threshold": "10.00", "condition": "below" }, "info": { "threshold": "20.00", "condition": "below" }, "alert_enabled": true } ``` **How it works**: Customer has a prepaid wallet starting with \$100. As balance depletes: * Balance reaches \$20 or less → Info alert (informational milestone, balance getting low) * Balance reaches \$10 or less → Warning alert (low balance, needs top-up soon) * Balance reaches \$0 or less → Critical alert (urgent, service will be interrupted) ### Use Case 2: Monthly Spending Limit Monitoring **Scenario**: Monitor spending against monthly spending limit **Configuration**: ```json theme={null} { "info": { "threshold": "100.00", "condition": "above" }, "warning": { "threshold": "500.00", "condition": "above" }, "critical": { "threshold": "1000.00", "condition": "above" }, "alert_enabled": true } ``` **How it works**: Monthly spending limit of \$1,000 set. As spending increases: * Spending reaches \$100 or more → Info alert (spending milestone reached) * Spending reaches \$500 or more → Warning alert (halfway to spending limit) * Spending reaches \$1,000 or more → Critical alert (spending limit reached, action required) ## Configuring Alerts via Dashboard Follow these steps to configure feature wallet balance alerts: ### Step 1: Navigate to Features 1. In the main navigation menu, select **Features** from **Product Catalogue** 2. Locate and select the feature you want to configure alerts for from the features list ### Step 2: Access Alert Settings 1. In the feature details page, click the **three-dot menu (⋮)** in the top right corner 2. Select **Alert Settings** from the dropdown menu Alert Settings Dropdown ### Step 3: Configure Alert Thresholds A dialog box titled "Feature Alert Settings" will appear with the following configuration options: Configure Feature Alert Settings #### Configuration Options **Enable Alerts** * Toggle the switch to activate balance monitoring for this feature * When disabled, no alerts will be triggered regardless of threshold settings **Alert Condition** * **Below**: Alert when balance is **less than or equal to** (≤) the threshold (for monitoring balance depletion) * **Above**: Alert when balance is **greater than or equal to** (≥) the threshold (for monitoring spending/usage) **Critical Threshold** (Required when Warning is set) * Enter the balance amount that triggers a critical alert * This is the highest priority alert level * Example for "below": \$0 (service will be interrupted) * Example for "above": \$1000 (spending limit reached) **Warning Threshold** (Optional, requires Critical) * Enter the balance amount that triggers a warning alert * Medium priority alert level * Example for "below": \$10 (low balance, needs attention) * Example for "above": \$500 (approaching spending limit) **Info Threshold** (Optional, can be standalone) * Enter the balance amount that triggers an info alert * Lowest priority alert level, informational only * Example for "below": \$20 (early warning indicator) * Example for "above": \$100 (usage milestone) **Save Changes** * Click to apply the alert configuration * Alerts will begin monitoring immediately after saving **Cancel** * Click to discard changes and close the dialog ### Step 4: Verify Alert Configuration After saving: 1. The feature will display an alert indicator showing alerts are enabled 2. Webhook notifications will be sent when thresholds are breached 3. You can view alert history in the feature's activity log ## State Transitions The alert system evaluates balance changes and transitions between states automatically: ### State Transition Examples **Scenario 1: Balance decreasing from \$50 (below condition)** Starting balance: \$50 (above all thresholds) 1. Balance reaches \$20 or less → **OK → Info** 2. Balance reaches \$10 or less → **Info → Warning** 3. Balance reaches \$0 or less → **Warning → In Alarm** 4. Balance increases to \$50 (top-up) → **In Alarm → OK** **Scenario 2: Rapid balance decrease (below condition)** Starting balance: \$100 (above all thresholds) 1. Large transaction: Balance reaches \$5 or less → **OK → Warning** (skips info, as \$5 is ≤ \$10 but > \$0) 2. Another transaction: Balance reaches -\$2 → **Warning → In Alarm** (balance is ≤ \$0) **Scenario 3: Usage increase (above condition)** Starting balance/usage: \$0 (below all thresholds) 1. Usage reaches \$100 or more → **OK → Info** 2. Usage reaches \$500 or more → **Info → Warning** 3. Usage reaches \$1000 or more → **Warning → In Alarm** 4. New billing cycle resets usage to \$0 → **In Alarm → OK** State transitions are unidirectional based on threshold crossings. The system never transitions "backwards" (e.g., Warning → Info) unless the balance crosses back over the threshold. ## Webhook Integration ### Webhook Event Type When an alert threshold is breached, the system sends a webhook with the following event type: ``` feature.wallet_balance.alert ``` ### Webhook Payload Structure ```json theme={null} { "alert_status": "in_alarm", "alert_type": "feature_wallet_balance", "event_type": "feature.wallet_balance.alert", "feature": { "id": "feat_01K8DBMBN87CY36DEFY62PEXBT", "name": "Alert Feat 1", "type": "metered", "alert_settings": { "alert_enabled": true, "critical": { "condition": "below", "threshold": "-15" }, "warning": { "condition": "below", "threshold": "-5" }, "info": { "condition": "below", "threshold": "0" } }, "status": "published", "meter_id": "meter_01K8DBMBN5NRFNYBMXN1G3WDWY", "created_at": "2025-10-25T09:36:40.616655Z", "updated_at": "2025-10-25T11:14:15.925198Z" }, "wallet": { "id": "wallet_01K8DC3ZAH6R55GTPFTWSR2EHF", "name": "Prepaid Wallet", "wallet_type": "PRE_PAID", "balance": "10", "credit_balance": "10", "currency": "usd", "alert_enabled": true, "alert_state": "in_alarm", "wallet_status": "active", "customer_id": "cust_01K8DBYJ0ST5GD57EB6EATEACE", "created_at": "2025-10-25T09:45:12.273036Z", "updated_at": "2025-10-25T09:58:22.259833Z" } } ``` ### Webhook Fields Explained | Field | Description | | ------------------------ | ----------------------------------------------------------- | | `alert_status` | Current alert state: `ok`, `info`, `warning`, or `in_alarm` | | `alert_type` | Type of alert: `feature_wallet_balance` | | `event_type` | Webhook event identifier: `feature.wallet_balance.alert` | | `feature.id` | Unique identifier of the feature | | `feature.name` | Name of the feature being monitored | | `feature.alert_settings` | Complete alert configuration including all thresholds | | `wallet.id` | Unique identifier of the wallet | | `wallet.balance` | Current wallet balance (total balance) | | `wallet.credit_balance` | Current credit balance | | `wallet.alert_state` | Current alert state of the wallet | | `wallet.currency` | Currency of the wallet | ### Webhook Behavior * **All alert states trigger webhooks**: Info, Warning, and Critical all send notifications * **Immediate delivery**: Webhooks are sent instantly when state transitions occur * **Retry logic**: Failed webhook deliveries are automatically retried (up to 3 attempts) * **Rate limiting**: Webhook delivery is rate-limited to prevent spam (10 webhooks/second max) ## Monitoring Implementation The system uses two complementary monitoring approaches: ### 1. Real-Time Monitoring * **Trigger**: Wallet transactions (credit/debit operations) * **Behavior**: Immediate state evaluation on balance changes * **Notifications**: Instant webhook delivery * **Use case**: Primary monitoring mechanism for active wallets ### 2. Cron-Based Monitoring * **Frequency**: Every 5 minutes * **Behavior**: Periodic balance checks across all feature wallets * **Purpose**: Catches edge cases and ensures no alert is missed * **Use case**: Backup monitoring for reliability ## Validation Rules When configuring alerts, the system enforces the following validation rules: **Required Fields** When `alert_enabled` is `true`, at least one threshold (critical, warning, or info) must be provided. ### Validation Error Examples **Error 1: Warning without Critical** ```json theme={null} { "warning": { "threshold": "10.00", "condition": "below" }, "alert_enabled": true } ``` ❌ **Error**: "critical threshold is required when warning threshold is provided" **Error 2: Incorrect Threshold Ordering** ```json theme={null} { "critical": { "threshold": "20.00", "condition": "below" }, "warning": { "threshold": "10.00", "condition": "below" }, "info": { "threshold": "0.00", "condition": "below" }, "alert_enabled": true } ``` ❌ **Error**: "info threshold must be greater than warning threshold for 'below' condition" **Error 3: No Thresholds Provided** ```json theme={null} { "alert_enabled": true } ``` ❌ **Error**: "at least one threshold (critical, warning, or info) is required when alert\_enabled is true" ## Best Practices **Set Critical Thresholds at Service Interruption Points** Configure critical thresholds at the exact point where service would be interrupted or severely degraded. **Use 2-5x Multiplier for Warning Thresholds** Set warning thresholds at 2-5 times the critical threshold to provide adequate warning time. **Use 5-10x Multiplier for Info Thresholds** Set info thresholds at 5-10 times the critical threshold for early tracking indicators. **Test Alert Thresholds in Staging** Always test alert configurations in a staging environment before deploying to production. **Document Threshold Decisions** Document why each threshold value was chosen for future reference and optimization. **Use "below" for Credit Depletion** Use the "below" condition when monitoring balance depletion or credit consumption. **Use "above" for Usage Limits** Use the "above" condition when monitoring spending, usage limits, or consumption milestones. ## Troubleshooting ### Alerts Not Triggering **Possible causes**: * `alert_enabled` is set to `false` * Threshold configuration is incorrect * Balance has not crossed the configured threshold * Webhook endpoint is not configured **Solution**: 1. Verify `alert_enabled` is `true` 2. Check threshold values and conditions 3. Review current wallet balance 4. Confirm webhook endpoint is configured correctly ### Wrong Alert State **Possible causes**: * Threshold ordering is incorrect for the condition * Multiple thresholds using different conditions * Balance calculation is incorrect **Solution**: 1. Verify threshold ordering matches condition (below: ascending, above: descending) 2. Ensure all thresholds use the same condition 3. Check wallet balance calculation logic ## Summary Feature Wallet Balance Alerts provide a comprehensive, flexible monitoring solution for tracking wallet balances across your feature offerings. Key takeaways: * **Three independent alert levels**: Critical, Warning, and Info * **Flexible configurations**: Standalone info, critical+info, or full stack * **Real-time monitoring**: Instant webhook notifications on threshold breaches * **Multiple monitoring approaches**: Real-time transaction monitoring + cron-based backup * **Comprehensive error handling**: Robust validation and error messages * **Production-ready**: Extensive logging, retry logic, and rate limiting By implementing Feature Wallet Balance Alerts, you can proactively monitor usage, prevent service interruptions, and maintain a superior customer experience. # Charges grouping Source: https://docs.flexprice.io/docs/product-catalogue/groups/charges-grouping Assign prices (charges) to a group when adding or editing a charge. Create a group with **Entity type** **Price** first ([Creating a group](/docs/product-catalogue/groups/create)). Then assign charges to that group using the steps below. ## Workflow * When adding a charge to a plan, use the **Group** field: search or select a group (e.g. "GPU Usage") * Save the charge; it is stored with that group Assign group to charges * Open the plan → **Charges** → **Edit Details** * In **Edit Price Details**, set **Group** to the desired group or **None** to ungroup * Save Update group in price ## API (prices) | Action | Method | Endpoint | | --------- | ------ | ------------------- | | Create | POST | `/v1/prices` | | Update | PUT | `/v1/prices/:id` | | Get price | GET | `/v1/prices/:id` | | Search | POST | `/v1/prices/search` | **Create:** Include `group_id` (group `id` from `POST /v1/groups`). **Update:** Send `group_id: "grp_..."` to assign, `group_id: ""` to ungroup. Omit to leave unchanged. **View:** `GET /v1/prices/:id?expand=groups` or `POST /v1/prices/search` with `expand: "groups"` for full `group` on each price. **Analytics:** `POST /v1/events/analytics` with `expand: ["price"]`. Group appears under each item's `price.group` when set. **Invoices:** Line items use the price's `group_id` to fetch group names. **Plan cloning** preserves groups. Cloned plan's prices keep the same group assignments as the source. # Creating a group Source: https://docs.flexprice.io/docs/product-catalogue/groups/create Create and list groups. Entity type is set once at creation and cannot be changed. A group has a single **Entity type** (Price or Feature), chosen when you create it. That type cannot be changed later. Price groups are used for plan charges. Feature groups are used for features. ## Steps to create a group * Go to **Product Catalog** → **Groups** * Use **Filter** and **Sort** as needed. **+ Add** creates a new group Groups list * Click **+ Add** * In the **Create Group** dialog, enter **Group Name**, **Lookup Key** (unique per tenant and environment), and select **Entity Type** (**Price** or **Feature**) * Click **Create Group** Create Group The list shows **Type** (Price or Feature) for each group. Groups list — feature groups ## API | Action | Method | Endpoint | | ----------- | ------ | ------------------- | | Create | POST | `/v1/groups` | | Get one | GET | `/v1/groups/:id` | | List/search | POST | `/v1/groups/search` | | Delete | DELETE | `/v1/groups/:id` | **Create:** Body must include `name`, `entity_type` (`"price"` or `"feature"`), `lookup_key`. Response returns `id`. Use it as `group_id` on prices or features. **List/search:** Filter by `entity_type`, `name`, `lookup_key`, and other supported fields. **Get one:** Returns the group with `entity_ids` (IDs of prices or features in that group). **Delete:** Soft-delete. Every entity in that group has its `group_id` cleared. When you delete a group, every price or feature in that group has its `group_id` cleared (no orphaned references). ## Validation | Scope | Rule | | --------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | **Group** | `entity_type` must be `"price"` or `"feature"`. `lookup_key` is required and must be unique per tenant and environment (published groups). | # Feature grouping Source: https://docs.flexprice.io/docs/product-catalogue/groups/feature-grouping Assign features to a group when creating or editing a feature. Create a group with **Entity type** **Feature** first ([Creating a group](/docs/product-catalogue/groups/create)). Then assign features to that group using the steps below. ## Workflow * Go to **Product Catalog** → **Features** → **Create Feature** * In the **Group** field, search or select a group (e.g. "Usage Pricing") * Save; the feature is stored with that group Create feature with group * Open the feature → **Edit** * In **Group**, set the desired group or **None** to ungroup * Save Edit feature — Group ## API (features) | Action | Method | Endpoint | | ----------- | ------ | --------------------- | | Create | POST | `/v1/features` | | Update | PUT | `/v1/features/:id` | | Get feature | GET | `/v1/features/:id` | | Search | POST | `/v1/features/search` | **Create:** Optional `group_id`. Group must exist and have `entity_type: "feature"`. Response includes `group_id` and `group`. **Update:** Send `group_id: "group_..."` to assign, `group_id: ""` to ungroup. Omit to leave unchanged. **Get:** Response includes `group_id` and `group` when the feature is in a group. **Search:** Each item has `group_id` and `group` when set. Filter by group: `field: "group_id", operator: "eq", value: { "string": "" }` (or `operator: "in"` with array). ### Where reflected **Usage Breakdown** in the analytics dashboard and [Customer Portal](/docs/customers/customer-portal#usage-tab) **Usage** tab shows usage and cost by group. Usage breakdown by group # Overview Source: https://docs.flexprice.io/docs/product-catalogue/groups/overview Create named groups (e.g. Usage Pricing, Add-on Charges) for features and prices, and see grouped invoice line-items, usage and cost broken down by group in analytics and the customer portal. Groups in Flexprice let you organize **prices** and **features** into named buckets—e.g. by product line, region, or tier. You can then filter and display them by group in invoices, catalogs, analytics, and the [customer portal](/docs/customers/customer-portal#usage-tab) Usage tab. Flexprice supports grouping for **prices** and **features**. Each group holds one entity type only (`entity_type: "price"` or `"feature"`). **Benefits:** * **Organize** — Bucket prices or features by product line, region, tier, or any dimension * **Filter** — Search groups by `lookup_key`, `name`, `entity_type`. List entities with group on each * **Charges (prices)** — Group charges in a plan. Grouped invoice line-items * **Features** — Group features for lists and filters. Feature search can filter by `group_id`. Usage breakdown in analytics and [customer portal](/docs/customers/customer-portal#usage-tab) reflects feature usage and cost by group ## API reference | Action | Method | Endpoint | | -------------------------------- | ------ | ------------------- | | Create group | POST | `/v1/groups` | | Get group | GET | `/v1/groups/:id` | | Query groups | POST | `/v1/groups/search` | | Delete group | DELETE | `/v1/groups/:id` | | Create price (with group) | POST | `/v1/prices` | | Update price (set/clear group) | PUT | `/v1/prices/:id` | | Create feature (with group) | POST | `/v1/features` | | Update feature (set/clear group) | PUT | `/v1/features/:id` | **Create group:** Body: `name`, `entity_type` (`"price"` or `"feature"`), `lookup_key`. Response returns `id` — use as `group_id` on prices or features. **Delete group:** Soft-delete. Every entity in that group has `group_id` cleared. When you delete a group, every price or feature in that group has its `group_id` cleared (no orphaned references). ## Validation | Scope | Rule | | ----------- | ---------------------------------------------------------------------------------------------------------------------------- | | **Group** | `entity_type` must be `"price"` or `"feature"`. `lookup_key` required; unique per tenant and environment (published groups). | | **Price** | If `group_id` is set, the group must exist, be published, and have `entity_type` `"price"`. | | **Feature** | If `group_id` is set, the group must exist, be published, and have `entity_type` `"feature"`. | ## What's next * [Create a group](/docs/product-catalogue/groups/create) — Choose Entity Type (Price or Feature), then assign entities * [Charges grouping](/docs/product-catalogue/groups/charges-grouping) — Assign prices to a group when adding or editing a charge * [Feature grouping](/docs/product-catalogue/groups/feature-grouping) — Assign features to a group when creating or editing a feature # Archiving a plan Source: https://docs.flexprice.io/docs/product-catalogue/plans/archive Over time, businesses may need to modify or discontinue certain pricing plans due to changes in product offerings, evolving customer needs, or strategic shifts. Instead of deleting plans, archiving allows you to retain historical data while preventing new customers from subscribing. This ensures that existing customers remain unaffected while new sign-ups are restricted. **Steps to archive a plan** * Navigate to the Plans Section in Product Catalog section * Click on the three-dot menu next to the plan you want to archive Archive Plan Option * Select Archive from the dropdown options * A confirmation dialog will appear asking if you want to archive the plan. Make sure that you can no longer modify the plan details once it is archived. Confirm Archive Plan * Click **Archive** to proceed, or **Cancel** if you change your mind. Customers already subscribed to the plan will continue using it until the end of their current billing cycle, after which they will need to transition to an active plan. # Flat fee Source: https://docs.flexprice.io/docs/product-catalogue/plans/billing-models/flat-fee The **Flat Fee** model charges a consistent rate for each unit of usage. This straightforward approach ensures predictability for both the business and the customer.​ **Example:** An API service charges **\$0.05 per API call**. If a customer makes **1,000 API calls** during a billing period, their total charge would be:​ `1,000 API calls × $0.05 = $50`​ This model is ideal when the cost per unit remains constant, regardless of the total usage volume. Screenshot 2025-03-09 at 2.27.20 AM.png # Package Source: https://docs.flexprice.io/docs/product-catalogue/plans/billing-models/package The **Package-Based** model applies a **fixed price to a defined range of usage**. Instead of charging per unit, the entire **range of units falls under a single fixed price**. **Example:** A **SMS service provider** applies the following package-based pricing: * **1 – 1,000 messages** → \$50 * **1,001 – 5,000 messages** → \$200 * **5,001 – 10,000 messages** → \$350 If a customer sends **4,500 messages**, they fall within the **1,001 – 5,000 messages package**, meaning they are charged **\$200**, regardless of the exact count. Even if the customer only sent 1,500 messages, they would still pay the full **\$200** for the 1,001 – 5,000 package. 💡 It is Best for Businesses that want to charge customers based on predefined consumption ranges rather than individual units. Screenshot 2025-03-09 at 2.29.27 AM.png # Volume tiered Source: https://docs.flexprice.io/docs/product-catalogue/plans/billing-models/volume-tiered In Flexprice, the **Volume-Tiered** model allows you to define multiple pricing tiers, where a single unit price is applied based on the total volume of usage. This means that the unit price is determined by the highest tier reached by the customer's total consumption. **Example:** Consider the following pricing tiers for an API service: | Tier | Usage Range (API Calls) | Unit Price (\$) | | ---------- | ----------------------- | --------------- | | **Tier 1** | 0 – 10,000 | 0.0010 | | **Tier 2** | 10,001 – 50,000 | 0.0008 | | **Tier 3** | 50,001 – 100,000 | 0.0006 | | **Tier 4** | 100,001 and above | 0.0004 | If a customer makes **65,000 API calls** in a billing period, their total usage falls within **Tier 3** (50,001 – 100,000 calls) with a unit price of **\$0.0006**. The total charge would be: `65,000 API calls × $0.0006 = $39`​ This model ensures that customers are charged a consistent rate for all units based on their total usage volume, which can either increase or decrease depending on the defined tiers. Screenshot 2025-03-09 at 2.28.10 AM.png # Advance vs Arrear Source: https://docs.flexprice.io/docs/product-catalogue/plans/charges/advance-vs-arrear Billing is a fundamental aspect of any business, determining when and how customers are charged for the products or services they use. The timing of billing can significantly impact cash flow, customer experience, and overall financial operations. Broadly, businesses charge customers in one of two ways: * **Advance Billing**: Customers pay before they receive a service. * **Arrear Billing**: Customers pay after they have used a service. Both approaches are widely used across industries, and neither is tied exclusively to a particular pricing model. Understanding when to use advance or arrear billing is key to designing a flexible and efficient billing system. This section explores how each method applies to **recurring pricing** and **usage-based pricing.** **Recurring charges**\ Recurring pricing follows a fixed billing cycle—monthly, quarterly, or more. Here, businesses decide whether to charge before or after each cycle. images/Screenshot2025-03-15at6.53.21PM.png * Advance billing * **In advance billing**, customers pay before the billing cycle starts, similar to a prepaid mobile plan where you recharge upfront before making calls or using data.In advance billing, customers **pay before** the billing cycle starts, similar to a prepaid mobile plan where you **recharge upfront** before making calls or using data.\ \ **Example:** * A SaaS company charges **\$100 on the 1st of every month** for access throughout the month. * A B2B software provider offers an **annual plan** where customers **pay for 12 months in advance**. * **Why businesses use advance billing?** * **Predictable revenue** – Ensures cash flow before delivering the service. * **No unpaid invoices** – Customers pay upfront, reducing collection risks. * **Customers expect it** – Just like prepaid plans, SaaS subscriptions are commonly prepaid. * Arrear billing * In arrear billing, customers use the service first and pay later, just like postpaid mobile plans where you get an invoice at the end of the month. **Example:** * A company provides **access to a software platform** and invoices customers at the **end of the month** for that period’s access. * An enterprise SaaS provider allows customers to **use the service for a quarter** and then **pays via Net-30 terms**. * **Why businesses use arrear billing?** * **Common in enterprise sales** – B2B customers often expect Net-30/Net-60 payment terms. * **Supports flexible contracts** – Customers commit upfront but settle payments later. * **Better for relationship-driven sales** – Businesses may delay billing to remove friction in customer acquisition. Usage-based charges\ Usage-based pricing follows a **metered** model, where charges depend on **how much of a service is consumed**. The common assumption is that **usage must always be billed in arrears**, but that’s not necessarily the case. images/Screenshot2025-03-15at6.55.35PM.png * Advance billing * Advance billing is also used in usage-based pricing, especially in **credit-based or commit-based models**, similar to how users **buy a data pack upfront before using mobile internet**. **Example:** * **Prepaid Credits**: An AI API charges **\$500 upfront for 1M tokens**, which customers use over time. * **Commit-Based Pricing**: A cloud provider requires a **minimum spend of \$10,000/month**, paid upfront, regardless of actual usage. * **Hybrid Prepaid Model**: A SaaS tool sells **prepaid API credits**, but if the user exceeds the limit, additional usage is billed in arrears. * **Why businesses use advance billing for usage?** * **Guaranteed cash flow** – Payments are received before service usage. * **Lowers default risk** – Eliminates unpaid bills from overconsumption. * **Encourages commitment** – Customers commit to volume usage, reducing churn. * Arrear billing * This is the standard choice for pay-as-you-go models, where customers **use the service first and are billed later**, just like a **postpaid mobile data plan**. **Example:** * A cloud provider (AWS, Azure) charges at the **end of each month** based on **actual compute/storage usage**. * A telecom provider bills customers at the end of the month for **call duration, data usage, and SMS count**. * **Why businesses use arrear billing?** * **Customers pay only for what they use** – No need to estimate usage in advance. * **Works well for unpredictable usage** – Suitable for cloud services and pay-as-you-go models. * **Preferred by enterprise clients** – Many businesses expect to be billed after consumption ### # Cloning a plan Source: https://docs.flexprice.io/docs/product-catalogue/plans/clone Create a duplicate of an existing plan with a new name and lookup key, including its active prices, published entitlements, and plan-scoped credit grants. Cloning creates a new plan that is a duplicate of an existing plan. Use it to version a plan (e.g. "Pro 2024" → "Pro 2025"), or experiment with a duplicate before changing the original. The new plan has a **distinct name**, and a **unique lookup key**; subscriptions and historical data are not copied. ## What gets copied * **Plan** — New plan in the same environment with the name and lookup key you provide; description, display order, and metadata can be overridden or copied from the source. * **Prices** — Only **active** (published and non-expired) plan prices. * **Entitlements** — Only **published** plan entitlements. * **Credit grants** — Only **published**, **plan-scoped** credit grants. ## What does not get copied * Subscriptions or line items * Expired prices * Draft or archived prices, entitlements, or grants Plan cloning is supported **within the same environment** only. The cloned plan is created in the same environment as the source plan. ## Steps to clone a plan * Go to **Product Catalog** → **Plans** * Open the plan you want to duplicate * Click the **three-dot menu (⋮)** in the top right corner * Select **Duplicate** from the dropdown menu Clone plan option * Enter **Plan Name** (required) * Enter **Lookup Key** (required, must be unique). It is auto-generated by default. * Optionally enter **Description** and **Metadata** * Click **Duplicate** Duplicate plan dialog If a published plan already exists with the lookup key you entered, the system will return an error and ask you to choose a different lookup key. A new plan is created with the same charges, entitlements, and credit grants as the source. You are directed to the cloned plan after plan cloning is successful. Cloned plan detail page showing Plan Details, Metadata with source_plan_id, and Overview tab Flexprice always adds **`source_plan_id`** to the cloned plan's metadata set to the source plan ID, so you can tell which plan it was duplicated from. ## Use cases ### Plan versioning * **Annual updates**: Clone "Pro 2024" to "Pro 2025" and adjust prices or features on the new plan while keeping the same structure. * **Product evolution**: Duplicate a plan before changing entitlements or credit grants so existing subscribers stay on the original. ### Experimentation and rollback * **Safe experimentation**: Clone a plan, change the duplicate, and only move customers over when ready. The source plan stays unchanged. * **Rollback**: Keep a copy of a plan before major changes so you can reference or re-use the previous configuration. ## Best practices 💡 **Use a unique lookup key**: Every cloned plan must have a lookup key that does not match any existing published plan. Use a naming pattern (e.g. `pro_2025`, `pro_plan_v2`) to avoid conflicts. 💡 **Set a clear name and description**: Give the cloned plan a descriptive name and, if needed, a description so your team can tell it apart from the source (e.g. "Pro 2024 v2 – cloned from Pro 2024"). 💡 **Use `source_plan_id` in metadata**: The cloned plan’s metadata always includes **`source_plan_id`**. Use it in reporting or automation to track plan lineage and which plans were duplicated from which. 💡 **Clone instead of editing live plans**: When you want to change structure (prices, entitlements, credit grants), clone the plan and edit the copy. Existing subscribers stay on the original; new sign-ups can use the new plan. 💡 **Review what gets copied**: Only active (non-expired) prices and published entitlements and plan-scoped credit grants are copied. Draft or archived items and subscriptions are not. Confirm the source plan has everything published before cloning. # Creating a plan Source: https://docs.flexprice.io/docs/product-catalogue/plans/create Follow the steps below to create a pricing plan in Flexprice: * In the main navigation menu, select **Plans** from *Product Catalog.* * Click **“Add”** at the top-right of the screen. Screenshot 2025-03-09 at 1.30.15 AM.png This opens a **two-step form** to configure your plan. **Step 1: Enter plan details** This step captures key identifiers for your pricing plan. * **Plan Name**: A descriptive name for the plan. This name will be visible to users. * **Lookup Key**: A unique identifier used in API calls and system integrations. * **Plan Description** *(Optional)*: Provides internal context about the plan. Screenshot 2025-03-09 at 1.33.52 AM.png **Step 2: Define plan charges** It defines how customers will be charged under this plan. You can add **one or multiple** charges while creating a plan. Screenshot 2025-03-09 at 1.35.31 AM.png * **Setting-up a recurring charge:** Recurring charges are fixed charges that your customers are billed at regular intervals (e.g., \$10/month subscription fee). Screenshot 2025-03-09 at 1.37.32 AM.png * **Select billing currency**  Choose the currency in which you want to bill your customers. **Flexprice supports all currencies**, enabling businesses to charge customers globally. * **Choose a billable period**  The **billing period** determines when invoices are generated. The **billing period impacts invoice generation, feature limits, and renewals**. You can select from the following plan intervals: * **Weekly** → Customers are billed once every week. * **Monthly** → The most common cycle, where customers are charged every month. * **Quarterly** → Charges occur every 3 months. * **Half-Yearly** → Customers are billed twice a year (every 6 months). * **Yearly** → Customers are billed once per year. * **Set the Price** Define the **amount** customers will be charged per billing period. The price is set based on the **selected currency and interval**. * **Choose billing timing**:Decide when the charge is applied within the billing cycle: * **Advance Billing** → Customers are charged **at the start** of each billing period. * **Arrears Billing** → Customers are charged **at the end** of each billing period. * **Adding trial period** *(Optional)*: * Toggle **on** to enable a **free trial** before billing starts. * Enter the **trial period duration** (in days). * After the trial ends, the first charge is applied based on the **billing timing** selected. * Click **Add** to save the recurring charge settings and apply them to the pricing plan. * **Setting-up a usage-based charge:** To incorporate usage-based charges into a plan, you can utilize existing metered features. For instance, you can create charges based on the number of API calls, the number of active users, transactions, compute time, etc. Screenshot 2025-03-09 at 1.54.14 AM.png * **Select a Metered Feature** Choose a metered feature that will determine how usage is measured and billed. Features must be pre-configured in your product catalog 💡 *Only metered features can be linked to usage-based charges.* * **Choose Billing Currency** Select the currency in which you want to bill your customers. **Flexprice supports all currencies**, enabling businesses to charge customers globally. * **Define the Billing Period** The **billing period** determines how frequently usage is calculated and charged. Available intervals include:💡 *The billing period must align with the subscription plan to ensure accurate invoicing.* * **Weekly** → Usage is tracked and billed every week. * **Monthly** → The most common cycle, where customers are charged based on monthly usage. * **Quarterly** → Charges occur every 3 months based on accumulated usage. * **Half-Yearly** → Customers are billed twice a year (every 6 months). * **Yearly** → Customers are billed once per year for total usage over the year. * **Select a Billing Model** Flexprice allows different pricing models for usage-based billing:💡 *The correct billing model ensures flexibility in pricing strategy and revenue optimization. To read more about different types of charges read in the [Billing Models](/docs/product-catalogue/plans/billing-models/flat-fee) section.* * **Flat Fee** → A fixed price per unit of usage (e.g., \$0.01 per API call). * **Volume-Based Pricing** → Price changes depending on the volume of usage (e.g., $0.01 per API call for the first 100K calls, then $0.008 for additional calls). * **Package-Based Pricing** → Customers purchase usage in predefined bundles (e.g., \$50 for 100K API calls). * **Set the Price per Unit** Define how much you want to charge **per unit of usage**. The unit is determined by the **metered feature selected** (e.g., per API call, per GB stored, per transaction). * **Choose Billing Timing** Select when the customer should be charged for usage:💡 *Arrears billing is common for metered services like cloud computing and APIs* * **Advance Billing** → Customers will be invoiced as soon as they use a particular feature * **Arrears Billing** → Customers are billed at the end of the cycle based on actual consumption. * Once all configurations are set, click **Add** to apply the usage-based charge to the pricing plan. * Click **Save** to finalize the pricing plan. # Localisation Source: https://docs.flexprice.io/docs/product-catalogue/plans/localisation As businesses scale globally, **pricing localization becomes critical** for acquiring and retaining customers across different regions. A **one-size-fits-all pricing strategy doesn’t work** when: * **Customers expect to pay in their local currency** rather than USD or EUR. * **Exchange rate fluctuations impact affordability** and purchasing decisions. * **Regional taxes and compliance requirements** require billing in local currencies. * **Enterprise customers negotiate contracts in multiple currencies**, making flexible invoicing essential. Flexprice provides **full multi-currency support** across both **recurring and usage-based charges**, allowing businesses to price their products flexibly across global markets. **Creating Plans with Multiple Currencies**\ With Flexprice, you don't need to create separate plans for each currency. When setting up pricing plans, you can define a**single plan with multiple currency options** → e.g., `$99 USD/month`, `€89 EUR/month`, `₹7,999 INR/month`. images/Screenshot2025-03-16at5.53.29PM.png **Assigning Subscriptions in a Specific Currency**\ While a single pricing plan can support multiple currencies, a customer’s subscription must be assigned in one currency.\ This is because: * **Invoices are generated per subscription.** Since invoices are currency-specific, each **subscription must have a single currency** for accurate billing, tax calculation, and revenue reporting. * To offer **multiple currencies to a customer, assign multiple subscriptions.** # Overview Source: https://docs.flexprice.io/docs/product-catalogue/plans/overview A **Pricing Plan** in Flexprice defines **how your product or service is priced and structured for customers**. Plans are a structured set of pricing rules that determine the cost and access of features for a customer. Whenever a customers subscribes to a plan, it governs their usage limits, billing terms, and available functionality. **Plans in Flexprice allow you to:** * **Monetise your product effectively:** Create any pricing model whether it is recurring, usage-based or hybrid based effortlessly. * **Define entitlements:** Control which Metered, Boolean, or Static Features are available in each plan. * **Enable pricing experimentation:** Iterate on pricing and entitlements without major code changes. * **Handle multi-currency billing:** Create pricing plan in any currency * **Automate trial management:** Offer trial periods before transitioning to paid plans. * **Configure advanced charges:** Supports flat fees, package-based pricing, and volume-based pricing. * **Granular plan management:** Easily edit, duplicate, and manage pricing plans as your product evolves. With Flexprice, you have full control over how your product is packaged, ensuring **transparent, scalable, and adaptable billing** for your customers. # Price Overrides Source: https://docs.flexprice.io/docs/product-catalogue/plans/price-overrides Learn how to customize pricing for individual customers by overriding plan charges during subscription creation. **Price Overrides** in Flexprice allow you to **customize pricing for specific customers** by modifying individual charges within a plan during subscription creation. This feature is particularly useful for offering special pricing, discounts, or custom rates for enterprise customers, early adopters, or promotional campaigns. ## When to Use Price Overrides Price overrides are ideal for scenarios such as: * **Enterprise deals** with custom pricing arrangements * **Promotional pricing** for specific customers or campaigns * **Volume discounts** applied at the customer level * **Trial or beta pricing** for early adopters * **Negotiated rates** for strategic partnerships ## How to Override Plan Prices ### Step 1: Navigate to Customer Subscription Creation * Go to **Billing** in the main navigation * Select **Customers** from the billing section * Select the customer you want to create a subscription for * Click **Add** to create a new subscription ### Step 2: Select a Plan * Choose the pricing plan from the dropdown menu * The system will display the subscription preview with all charges ### Step 3: Access the Charges Table * In the subscription preview, you'll see a **table of charges** showing all recurring and usage-based charges for the selected plan * Each charge row displays the original pricing from the plan ### Step 4: Override Individual Charges * Click the **three dots (⋮)** menu on any charge row * Select **"Override Charge"** from the dropdown menu Price Override Dropdown ### Step 5: Set the Override Amount * A dialog titled **"Override Price"** will open * The dialog shows: * **Original Price**: The default price from the plan (read-only) * **Override Amount (USD)**: Input field where you can enter the new price * **Cancel** and **Override Price** buttons Override Price Dialog * Enter the **new price** in the Override Amount field * Click **"Override Price"** to save the change ### Step 6: Review and Save * The charges table will update to show the **overridden prices** * You can override multiple charges within the same subscription * Review the **total subscription cost** with all overrides applied * Click **"Add Subscription"** to finalize the subscription with custom pricing Overridden Prices Summary ## Important Notes * **Plan Integrity**: Price overrides only affect the specific subscription being created. The original plan pricing remains unchanged for other customers. * **Billing Consistency**: Overridden prices will be used for all future billing cycles unless the subscription is modified. * **Audit Trail**: All price overrides are tracked and can be reviewed in the subscription history. * **Currency Support**: Price overrides work with all supported currencies in your Flexprice account. ## Best Practices * **Document Overrides**: Keep records of why specific overrides were applied for business continuity. * **Regular Reviews**: Periodically review overridden subscriptions to ensure pricing remains appropriate. * **Clear Communication**: Ensure your team understands when and how to use price overrides. * **Validation**: Double-check override amounts before saving to avoid billing errors. Price overrides provide the flexibility needed for complex pricing scenarios while maintaining the structure and consistency of your pricing plans. # Pricing Plans Source: https://docs.flexprice.io/docs/product-catalogue/plans/pricing Learn how to create and manage pricing plans in Flexprice, including step-by-step creation, billing models, and price termination management. # Pricing Plans A **pricing plan** defines how customers are billed for using your product or service. Flexprice provides a **comprehensive and customizable** pricing system that enables businesses to design billing structures that fit their monetization strategies. With **recurring, usage-based, and hybrid** pricing models, companies can launch new pricing plans seamlessly and iterate quickly. ## **Key Features of the Pricing System** * **Flexible Pricing Models**: Supports recurring subscriptions, metered billing, and hybrid plans. * **Multiple Billing Periods**: Options for daily, weekly, monthly, or yearly cycles. * **Multi-Currency Support**: Currently supports INR and USD. * **Automated Trial Management**: Enables businesses to offer trial periods before converting to paid plans. * **Advanced Charge Configurations**: Supports flat fees, package-based pricing, and volume-based pricing. * **Scalable & API-Driven**: Seamless integration with business operations and customer billing workflows. * **Granular Control Over Plans**: Edit, duplicate, and manage pricing plans with ease. ## **Use Cases** Flexprice is designed for a wide range of SaaS businesses, AI infrastructure providers, and digital platforms that need: * **Subscription-based billing** for SaaS products. * **Usage-based billing** for AI services, cloud resources, or API metering. * **Hybrid billing models** combining subscriptions with consumption-based charges. *** ## **Creating a Pricing Plan** Follow the steps below to create a pricing plan in Flexprice: ### **Navigate to Pricing Plans** 1. Log in to your **Flexprice Dashboard**. 2. In the left sidebar, go to **Customer Management > Pricing Plan**. 3. Click **"Add Pricing Plan"** at the top-right of the screen. Add Pricing Plan This opens a **three-step form** to configure your plan. *** ## **Step 1: Enter plan details** This step captures key identifiers for your pricing plan. * **Plan Name**: A descriptive name for the plan. This name will be visible to users. * **Plan Slug**: A unique identifier used in API calls and system integrations. * **Plan Description** *(Optional)*: Provides internal context about the plan. Plan Details Form Plan Configuration ## **Step 2: Configure Billing Preferences** Billing preferences define when and how the customer will be billed. It includes: * **Choose billing timing**: * **Advance:** Customers are billed at the beginning of the billing period. * **Arrears:** Customers are billed at the end of the billing period based on actual usage. * **Adding trial period** *(Optional)*: * Toggle **on** to enable a free trial. * Enter the **trial period duration** in days. * Click **Next** to continue. Billing Preferences ## **Step 3: Define plan charges** It defines how customers will be charged under this plan. You can add **one or multiple** charges while creating a plan. * **Select Subscription Type:** * **Recurring Charges**: Fixed charges billed at regular intervals (e.g., \$10/month subscription fee). * **Usage-Based Charges**: Charges based on actual consumption (e.g., $0.01 per API call, $5 per 1,000 tokens). You can create a plan by adding multiple usage-based charges in the same plan as well Multiple Charges ### Setting-up a recurring-based charge: Select subscription type as Recurring to charge your customers a recurring fee based on the defined billing period. Whenever user purchases this plan, it will be charged a flat fee and given access to the features that will be part of that plan. Recurring Charge * **Select billing currency** We currently support 2 currencies which includes: * INR (Indian Rupees) * USD (United States Dollars) * **Choose a billable period** The plan interval corresponds to the billing period and defines when invoices are generated. There are several plan intervals: * **Weekly**: subscription fees and charges are billed on a weekly basis (Monday to Sunday); * **Monthly**: subscription fees and charges are billed on a monthly basis; * **Quarterly**: subscription fees and charges are billed on a quarterly basis (every 3 months); and * **Yearly**: subscription fees are billed on a yearly basis and charges can be billed monthly or annually. * Add relevant price you want to charge your customer based on the selected billing period and then click on **Add** button. ### Setting-up a usage-based charge: To incorporate usage-based charges into a plan, you can utilize existing billable metrics. This enables you to offer "pay-as-you-go" features. For instance, you can create charges based on the number of API calls, the number of active users, transactions, compute time, etc. * Select the billable metric on which you've created earlier. * Define relevant currency and billing period for your plan * Select the pricing model. Flexprice currently support 3 types of pricing models: * **Flat fee** Select the Flat fee pricing model if you want to charge the **same price for each unit** consumed. **Example:** \$0.01 per API call. Flat Fee Pricing * **Package pricing** Select the package charge model if you want to apply a **fixed price to a range of units**. **Example:** \$5 per 100 API calls. Package Pricing * **Volume tiered based pricing** Select the volume charge model if you want to define several price tiers but want to apply a **single unit price based on the total volume**. You can also apply a **flat fee** in addition to the unit price. **Example:**- 0 – 10,000 calls → \$0.001 per call * 10,001 – ∞ calls → \$0.0008 per call Volume Tiered Pricing * Click on **Add** to save the charges ### Setting-up hybrid charges: You can create a single pricing plan which includes multiple charges - Recurring as well usage-based charges. Hybrid Charges ### **Review & Save** * Double-check all entered details. * Click **Save** to finalize the pricing plan. *** ## **Managing Plan Prices After Creation** ### **Terminating Plan Prices** Once a pricing plan is created and has active subscriptions, you may need to terminate specific prices for various business reasons such as: * **Price increases or decreases** * **Feature deprecation** * **Plan restructuring** * **Regulatory compliance** * **Business model changes** #### **Immediate Price Termination** To immediately terminate a price: 1. **Navigate to the specific price** in your plan 2. **Use the DELETE API endpoint**: `DELETE /prices/{price_id}` 3. **The price will be terminated immediately** and marked with the current timestamp as the end date **API Example:** ```bash theme={null} curl -X DELETE "https://api.flexprice.io/v1/prices/{price_id}" \ -H "Authorization: Bearer YOUR_API_KEY" ``` #### **Scheduled Price Termination** For better business continuity, you can schedule price termination in the future: 1. **Use the DELETE API endpoint** with an `end_date` parameter 2. **Set the end\_date to a future date** when you want the price to terminate 3. **Existing subscriptions will continue to use the price** until the specified end date **API Example with Future End Date:** ```bash theme={null} curl -X DELETE "https://api.flexprice.io/v1/prices/{price_id}" \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "end_date": "2025-12-31T23:59:59Z" }' ``` **Benefits of Scheduled Termination:** * **Customer notification time** - Gives customers advance notice of price changes * **Billing continuity** - Prevents disruption to existing subscriptions * **Compliance** - Meets regulatory requirements for price change notifications * **Customer experience** - Allows customers to plan for changes #### **Price Termination Behavior** When a price is terminated: * **Active subscriptions** continue to use the terminated price until the end date * **New subscriptions** cannot be created with the terminated price * **Billing continues normally** until the termination date * **Line items are automatically ended** when the price expires ### **Synchronizing Plan Prices with Existing Subscriptions** **⚠️ Important: After terminating prices, you must run the Plan Price Sync API to ensure existing subscriptions reflect the changes.** #### **Why Plan Price Sync is Required** When you terminate a price in a plan: 1. **Existing subscriptions still reference the old price** 2. **Line items need to be updated** to reflect the termination 3. **Billing calculations must be adjusted** for the new pricing structure 4. **Override prices and customizations** need to be preserved #### **Running Plan Price Sync** Use the Plan Price Sync API to synchronize terminated prices across all existing subscriptions: **API Endpoint:** `POST /plans/{plan_id}/sync/subscriptions` **Example:** ```bash theme={null} curl -X POST "https://api.flexprice.io/v1/plans/{plan_id}/sync/subscriptions" \ -H "Authorization: Bearer YOUR_API_KEY" ``` #### **What Plan Price Sync Does** The sync process: 1. **Identifies all active subscriptions** using the plan 2. **Processes each subscription** to update line items 3. **Ends line items for terminated prices** (unless overridden) 4. **Creates line items for new active prices** (if applicable) 5. **Preserves subscription-specific overrides** and customizations 6. **Maintains billing continuity** throughout the transition #### **Best Practices for Price Termination** 1. **Plan Ahead** * Schedule terminations well in advance * Consider customer notification periods * Plan for business continuity 2. **Use Scheduled Termination** * Avoid immediate termination when possible * Give customers time to adjust * Maintain billing stability 3. **Always Run Plan Price Sync** * Run sync after any price changes * Ensure existing subscriptions are updated * Verify billing accuracy 4. **Monitor the Process** * Check sync results and statistics * Verify line item updates * Test billing calculations 5. **Preserve Customer Relationships** * Communicate changes clearly * Honor existing commitments * Provide migration paths #### **Monitoring Sync Results** The Plan Price Sync API returns detailed statistics: ```json theme={null} { "message": "Plan prices synchronized successfully", "plan_id": "plan_123", "plan_name": "Premium Plan", "synchronization_summary": { "subscriptions_processed": 150, "prices_added": 25, "prices_removed": 10, "prices_skipped": 115 } } ``` **Key Metrics:** * **Subscriptions Processed**: Total number of subscriptions updated * **Prices Added**: New line items created for active prices * **Prices Removed**: Line items ended for terminated prices * **Prices Skipped**: Line items that didn't require changes #### **Common Scenarios** **Scenario 1: Price Increase** 1. Terminate old price with future end date 2. Create new price with higher amount 3. Run plan price sync to update subscriptions 4. Customers continue with old price until end date 5. New billing period uses new price **Scenario 2: Feature Deprecation** 1. Terminate price for deprecated feature 2. Run plan price sync to end related line items 3. Existing subscriptions lose access to feature 4. New subscriptions cannot include deprecated feature **Scenario 3: Plan Restructuring** 1. Terminate multiple prices simultaneously 2. Create new pricing structure 3. Run plan price sync to update all subscriptions 4. Ensure billing continuity during transition ### **Troubleshooting Price Termination** #### **Common Issues** 1. **Sync Failures** * Check plan ID validity * Verify API permissions * Review error logs for specific issues 2. **Incomplete Updates** * Verify all prices are properly terminated * Check for override prices that may interfere * Ensure sync completed successfully 3. **Billing Discrepancies** * Review sync statistics * Check individual subscription line items * Verify price end dates are set correctly #### **Validation Rules** * **End date must be in the future** for scheduled termination * **Price must exist** and be accessible * **Plan must have active subscriptions** for sync to be meaningful * **API key must have appropriate permissions** ### **API Reference** **Delete Price:** * **Endpoint:** `DELETE /prices/{price_id}` * **Body:** `{"end_date": "2025-12-31T23:59:59Z"}` (optional) * **Response:** `{"message": "price deleted successfully"}` **Plan Price Sync:** * **Endpoint:** `POST /plans/{plan_id}/sync/subscriptions` * **Response:** Detailed synchronization summary with statistics **Get Plan:** * **Endpoint:** `GET /plans/{plan_id}` * **Use:** To verify plan structure before and after changes *** *For technical implementation details and advanced use cases, refer to the [API Reference](/api-reference//plans/create-plan).* # Updating a Plan Price Source: https://docs.flexprice.io/docs/product-catalogue/plans/update-price Update a plan price's amount, tiers, or metadata — and safely propagate the change to all active subscribers using the price sync workflow. Updating a plan price changes how it is charged going forward. Depending on what you change, the system either mutates the price in place or creates a new price version. After updating pricing fields, you need to run the price sync workflow to propagate the change to existing subscribers. ## The Update Price API ``` PUT /prices/{id} ``` All fields are optional. Only include the fields you want to change. ### Available Fields | Field | Type | Notes | | -------------------- | --------- | ------------------------------------------------------- | | `amount` | decimal | New unit price (FIAT prices) | | `billing_model` | string | `FLAT_FEE`, `TIERED`, or `PACKAGE` | | `tier_mode` | string | `VOLUME` or `SLAB` — for TIERED model | | `tiers` | array | New tier breakpoints (TIERED/FIAT) | | `transform_quantity` | object | Package billing: `divide_by` and `round` | | `price_unit_amount` | decimal | New unit price (CUSTOM price unit) | | `price_unit_tiers` | array | New tiers (CUSTOM price unit + TIERED) | | `display_name` | string | Display label shown on invoices | | `description` | string | Internal description | | `lookup_key` | string | Unique lookup key | | `metadata` | object | Key-value metadata | | `group_id` | string | Assign/reassign to a price group; `""` clears the group | | `effective_from` | timestamp | When the new price version takes effect (default: now) | ### Fields You Cannot Change These are immutable after a price is created: | Field | Why | | ----------------------------------------- | ---------------------------------- | | `type` | Cannot switch FIXED ↔ USAGE | | `currency` | Currency is locked at creation | | `billing_period` / `billing_period_count` | Billing cycle is fixed | | `billing_cadence` | Cannot switch recurring ↔ one-time | | `invoice_cadence` | ARREAR/ADVANCE is fixed | | `meter_id` | The usage metric cannot be swapped | | `price_unit_type` | Cannot switch FIAT ↔ CUSTOM | | `entity_type` / `entity_id` | Price ownership is fixed | ## Two Update Paths ### Path A — Metadata-only update (in-place) If you only update `display_name`, `description`, `lookup_key`, `metadata`, or `group_id`, the price is updated **in place**. No new price version is created. No sync is needed. ```json theme={null} PUT /prices/price_abc { "display_name": "API Calls (v2)", "metadata": { "tier": "enterprise" } } ``` Response: the updated price object. Done. ### Path B — Pricing field update (new version created) If you update any pricing field (`amount`, `billing_model`, `tiers`, `tier_mode`, `transform_quantity`, `price_unit_amount`, `price_unit_tiers`), the system: 1. **Terminates the current price** — sets its `end_date` to `effective_from` (or now if not provided) 2. **Creates a new price** — starts exactly at `effective_from`, with the updated values 3. Returns the **new price** in the response (with a new `id`) ```json theme={null} PUT /prices/price_abc { "amount": "49.99", "effective_from": "2026-04-01T00:00:00Z" } ``` Response: the new price object (`id` will be different from the original). Save the new price `id` from the response. Existing subscription line items still reference the old price ID. The sync workflow creates new line items referencing the new price. ## Syncing the Change to Existing Subscribers After a pricing field update, existing subscriptions are **not automatically updated**. You must run the price sync workflow to propagate the new price to all active subscribers on the plan. ### Why Manual Sync? The sync workflow can affect large numbers of subscriptions. Triggering it automatically on every price update would be unpredictable. Doing it explicitly gives you control over timing. ### Safe Sync Sequence **Step 1 — Update the price** ```json theme={null} PUT /prices/{price_id} { "amount": "49.99", "effective_from": "2026-04-01T00:00:00Z" } ``` Note the plan ID associated with this price (needed for sync). **Step 2 — Check for a running sync** ```json theme={null} POST /workflows/search { "workflow_type": "PriceSyncWorkflow", "entity_id": "", "workflow_status": "Running" } ``` * If `pagination.total > 0` — a sync is already running. Do not trigger another. Poll `GET /workflows/{workflow_id}/{run_id}` every 30 seconds until `status` is `Completed` or `Failed`, then continue to Step 3. * If `pagination.total == 0` — proceed to Step 3. **Step 3 — Trigger sync** ``` POST /plans/{plan_id}/sync/subscriptions ``` Response: ```json theme={null} { "workflow_id": "PriceSyncWorkflow-plan_xxx", "run_id": "run_abc123", "message": "price sync workflow started successfully" } ``` **Step 4 — Poll until complete** ``` GET /workflows/{workflow_id}/{run_id} ``` Poll every 30 seconds. Terminal states: | Status | Action | | -------------------------------------- | ----------------------------------------------- | | `Completed` | Done — all subscribers updated | | `Failed` | Check error details, fix root cause, re-trigger | | `Canceled` / `Terminated` / `TimedOut` | Re-trigger sync | **Step 5 — Verify (optional)** Check the sync summary in the completed workflow response: ```json theme={null} { "summary": { "line_items_found_for_creation": 120, "line_items_created": 120, "line_items_terminated": 120 } } ``` `line_items_terminated` = old price line items closed. `line_items_created` = new price line items added. ## What Sync Does After a Price Update When you update a price, the old price gets an `end_date` and a new price is created. The sync workflow: 1. **Terminates** existing line items that reference the old price (their `end_date` is set to match the old price's `end_date`) 2. **Creates** new line items for the new price on all subscriptions that don't have one yet Subscriptions with **overridden** line items (custom pricing) are **not affected** — their line items point to subscription-scoped prices, not the plan price you just updated. ## Using `effective_from` `effective_from` controls when the old price expires and the new one starts. | Scenario | Recommendation | | --------------------------------------------------- | ---------------------------------------------------------------- | | Change takes effect immediately | Omit `effective_from` (defaults to now) | | Change takes effect at start of next billing period | Set `effective_from` to the next period start | | Scheduled future price change | Set `effective_from` to the future date, trigger sync in advance | The sync workflow creates new line items with `start_date = price.start_date`. If you set `effective_from: "2026-04-01"`, the new line items will start on April 1 and the old ones will end on April 1. ## Examples ### Update a flat fee amount ```json theme={null} PUT /prices/price_monthly_base { "amount": "79.00", "effective_from": "2026-04-01T00:00:00Z" } ``` Then sync: `POST /plans/plan_growth/sync/subscriptions` ### Update usage tiers ```json theme={null} PUT /prices/price_api_calls { "billing_model": "TIERED", "tier_mode": "VOLUME", "tiers": [ { "up_to": 50000, "unit_amount": "0.002" }, { "up_to": 200000, "unit_amount": "0.001" }, { "up_to": null, "unit_amount": "0.0005" } ] } ``` ### Update metadata only (no sync needed) ```json theme={null} PUT /prices/price_api_calls { "display_name": "API Calls — Updated Tiers", "metadata": { "updated_by": "billing-team" } } ``` No sync required. ## Related Topics * [Subscription Line Item Overrides](/docs/subscriptions/subscription-line-item-overrides) * [Plan Price Overrides](/docs/subscriptions/plan-price-overrides) * [Plan Pricing](/docs/product-catalogue/plans/pricing) * [Billing Models](/docs/product-catalogue/plans/billing-models/flat-fee) # Clone Cursor pricing Source: https://docs.flexprice.io/docs/product-catalogue/plans/use-cases/clone-cursor-pricing Cursor’s pricing model combines subscription tiers with usage-based metering, allowing developers and teams to scale AI-powered assistance effectively. Cursor charges users based on their selected plan (Free, Pro, or Team) and additional metered usage for AI completions, codebase indexing, and customization. By setting this up in Flexprice, you can provide users with transparent billing while managing limits dynamically. This guide walks you through setting up Cursor’s pricing model using a structured approach, ensuring clarity in billing and monetization. **Use Cases** * AI-powered development tools * Code completion and AI assistance platforms * Team-based coding environments with AI-driven automation Whether you're a solo developer or a growing team, replicating Cursor’s pricing in Flexprice ensures seamless billing automation and monetization. This guide walks you through the setup process step by step. **Cursor’s pricing model** Cursor provides three pricing tiers: | Plan Name | Price per Month | Features Included | | --------- | --------------- | --------------------------------------------------------------------------------- | | Hobby | \$0 | 2,000 completions, 50 slow premium requests | | Pro | \$20 | Unlimited completions, 500 fast premium requests, unlimited slow premium requests | | Business | \$40 per user | Pro features + privacy mode, centralized billing, admin dashboard, SAML/OIDC SSO | In addition, Cursor provides usage-based metering for: * AI completions * Premium requests (fast and slow) **Configuring Cursor’s Pricing in Flexprice** 1. **Define **[**Features**](/docs/product-catalogue/features/create)** in Flexprice**\ Flexprice allows you to define different types of features to enable precise billing and usage tracking for Cursor's pricing model.\ \ **Metered Features** These features track usage over time and are billed based on consumption. | Feature Name | Feature Type | Aggregation Method | Key Filters | | --------------------- | ------------ | ------------------ | --------------------- | | AI Completions | Metered | SUM | Feature: AI Queries | | Fast Premium Requests | Metered | SUM | Feature: Premium Fast | | Slow Premium Requests | Metered | SUM | Feature: Premium Slow | **Boolean Features (On/Off Toggle)**\ These features enable or disable specific functionality without tracking usage. | Feature Name | Feature Type | Description | | --------------- | ------------ | -------------------------------------- | | Privacy Mode | Boolean | Enables privacy mode for organizations | | Admin Dashboard | Boolean | Grants access to admin-level analytics | | SAML/OIDC SSO | Boolean | Enables single sign-on authentication | **Static Features**\ These features are always included in a specific plan and do not change based on usage. | Feature Name | Feature Type | Description | | ------------- | ------------ | ------------------------------------- | | SAML/OIDC SSO | Static | Enables single sign-on authentication | 2. **Create \*\* [**Plans**](/docs/product-catalogue/plans/create)** for Subscription Tiers\*\* 1. Each of Cursor’s plans can be created as a **recurring charges** in Flexprice: | Plan Name | Billing Type | Base Price | | --------- | ------------ | --------------- | | Hobby | Subscription | \$0 | | Pro | Subscription | \$20/month | | Business | Subscription | \$40/user/month | 2. Define usage-based charges along with recurring charges\\ images/Screenshot2025-03-16at11.45.29PM.png # Clone OpenAI pricing Source: https://docs.flexprice.io/docs/product-catalogue/plans/use-cases/clone-openai-pricing Manually managing per-token pricing can be complex, but with Flexprice, you can automate and scale your billing effortlessly. This guide walks you through setting up OpenAI's O1 pricing model using a **package-based pricing approach**. This method ensures clarity in billing, making it ideal for AI APIs, generative models, and machine learning services. **Use Cases** * AI APIs (LLMs like OpenAI, Anthropic, Mistral) * Machine learning inference services * Text-to-Speech or Speech-to-Text APIs **OpenAI** charges users based on the number of input, output and cached tokens processed by their models. You can view the official pricing details here: [OpenAI Pricing](https://openai.com/api/pricing/). The O1 model has the following pricing structure: | **Token Type** | **Price per Million Tokens** | | ------------------- | ---------------------------- | | Input Tokens | \$15.00 per million tokens | | Output Tokens | \$60.00 per million tokens | | Cached Input Tokens | \$7.50 per million tokens | For example, if a user processes: * 5 million input tokens → $75.00 ($15.00 x 5) * 2 million output tokens → $120.00 ($60.00 x 2) * 1 million cached input tokens → $7.50 ($7.50 x 1) * Total Cost = \$202.50 Now, let’s configure the pricing for the O1 model using **Flexprice**. **Configuring Pricing of o1 model in Flexprice** 1. **Create** [Metered Features](/docs/product-catalogue/features/create) **for Token Usage** Since token usage is metered, we first define three separate Metered Features in Flexprice for input tokens, output tokens, and cached input tokens. | **Feature Name** | **Feature Type** | **Aggregation Method** | **Key** | **Filters** | | ------------------- | ---------------- | ---------------------- | ----------- | --------------------------------------------- | | Input Tokens | Metered | SUM | model\_name | model: OpenAI O1, prompt\_type: input | | Output Tokens | Metered | SUM | model\_name | model: OpenAI O1, prompt\_type: output | | Cached Input Tokens | Metered | SUM | model\_name | model: OpenAI O1, prompt\_type: cached\_input | 2. **Create a Plan with** [Package-Based Pricing](/docs/product-catalogue/plans/billing-models/package) Once the metered features are created, we define a **Plan** that charges users per million tokens rather than per individual token. | **Metered Feature** | **Billing Model** | Charges | | ------------------- | ----------------- | -------------------------- | | Input Tokens | Package Charge | \$15.00 per million tokens | | Output Tokens | Package Charge | \$60.00 per million tokens | | Cached Input Tokens | Package Charge | \$7.50 per million tokens | Now, whenever a customer **purchases this plan and starts using it**, they will: * See **real-time usage events** for token consumption. * Get a **dynamically generated proposed invoice** based on their usage. * Have full transparency in billing, ensuring clarity on costs. This process ensures that **AI companies can charge users fairly based on actual usage** while providing predictable and scalable billing. # Apply Tax to Customers and Subscriptions Source: https://docs.flexprice.io/docs/product-catalogue/taxes/apply Link tax rates to tenants, customers, and subscriptions. Manage post-creation changes via the subscription modification API. A **tax association** is the link between a tax rate and an entity. flexprice supports associations at three scopes: tenant (default for all customers), customer (overrides tenant for that customer), and subscription (overrides customer for that subscription's invoices). ## Association fields | Field | Type | Required | Description | | ---------------------- | --------- | -------- | ----------------------------------------------------------------------------------- | | `tax_rate_code` | string | Yes | The `code` of the tax rate to link | | `entity_type` | string | No | `tenant`, `customer`, `subscription`, or `invoice` | | `entity_id` | string | No | ID of the entity to link | | `external_customer_id` | string | No | Your own customer ID, usable instead of `entity_id` for customer-level associations | | `auto_apply` | bool | No | Set to `true` to apply this tax automatically on every new invoice | | `priority` | int | No | Lower number fires first. Default: `0` | | `currency` | string | No | Scopes this association to a specific currency, e.g. `"USD"` | | `start_date` | timestamp | No | When the association becomes active. Defaults to now. | | `end_date` | timestamp | No | When the association expires. Omit for indefinite. | ## Apply to a tenant (default for all customers) A tenant-level association acts as the default tax for every customer that has no association of its own. ```bash theme={null} curl -X POST https://api.flexprice.io/v1/taxes/associations \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "tax_rate_code": "TAX_US_CA", "entity_type": "tenant", "entity_id": "", "auto_apply": true, "currency": "USD", "priority": 0 }' ``` **Response** ```json theme={null} { "id": "txa_01abc123", "tax_rate_id": "txr_01abc456", "entity_type": "tenant", "entity_id": "", "auto_apply": true, "currency": "USD", "priority": 0 } ``` ## Apply to a specific customer Customer-level associations override tenant-level ones for that customer only. ```bash theme={null} curl -X POST https://api.flexprice.io/v1/taxes/associations \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "tax_rate_code": "TAX_EU_VAT", "entity_type": "customer", "external_customer_id": "your-customer-id", "auto_apply": true, "currency": "EUR", "priority": 0 }' ``` ## Apply at subscription creation Pass `tax_rate_overrides` in the create subscription request to link rates directly to that subscription. These override any customer or tenant associations for that subscription's invoices. ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "customer_id": "", "plan_id": "", "start_date": "2025-01-01T00:00:00Z", "tax_rate_overrides": [ { "tax_rate_code": "TAX_US_CA", "currency": "USD" } ] }' ``` When `tax_rate_overrides` is set, flexprice uses only those rates for the subscription's invoices. Customer and tenant associations are not inherited. Omit `tax_rate_overrides` entirely to inherit from the customer. **`tax_rate_overrides` fields** | Field | Type | Required | Description | | --------------- | ------ | -------- | -------------------------------------- | | `tax_rate_code` | string | Yes | The `code` of the tax rate to link | | `currency` | string | Yes | ISO currency code, e.g. `"USD"` | | `auto_apply` | bool | No | Defaults to `true` | | `priority` | int | No | Lower number fires first. Default: `0` | | `metadata` | object | No | Key-value pairs for your own tracking | ## Add or remove tax after subscription creation Use the subscription modification API with `type: "tax"`. ### Add a tax rate ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions//modify/execute \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "type": "tax", "tax_params": { "action": "add", "tax_rate_id": "", "effective_date": "2025-06-01T00:00:00Z" } }' ``` `effective_date` is optional. Omit it to apply the change immediately. ### Remove a tax rate ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions//modify/execute \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "type": "tax", "tax_params": { "action": "remove", "tax_association_id": "" } }' ``` Get the `tax_association_id` from `GET /v1/taxes/associations?entity_type=subscription&entity_id=`. Removing a tax association affects only invoices generated after the change. Previously issued invoices are not changed. ### Preview before executing Swap `/modify/execute` for `/modify/preview` to see the effect without committing: ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions//modify/preview \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "type": "tax", "tax_params": { "action": "add", "tax_rate_id": "" } }' ``` ## tax\_params fields | Field | Type | Required | Description | | -------------------- | --------- | -------------------- | ---------------------------------------------- | | `action` | string | Yes | `add` or `remove` | | `tax_rate_id` | string | When `action=add` | ID of the tax rate to attach | | `tax_association_id` | string | When `action=remove` | ID of the tax association to detach | | `effective_date` | timestamp | No | When the change takes effect. Defaults to now. | ## Multiple tax rates on the same entity Create one association per rate. Each rate is applied independently to the same taxable base and does not compound. ```bash theme={null} # State tax (higher priority) curl -X POST https://api.flexprice.io/v1/taxes/associations \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "tax_rate_code": "TAX_STATE", "entity_type": "customer", "entity_id": "", "auto_apply": true, "currency": "USD", "priority": 0 }' # Federal tax (lower priority) curl -X POST https://api.flexprice.io/v1/taxes/associations \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "tax_rate_code": "TAX_FEDERAL", "entity_type": "customer", "entity_id": "", "auto_apply": true, "currency": "USD", "priority": 1 }' ``` If the taxable amount is $100, state at 6% adds $6 and federal at 2% adds $2 for a total tax of $8. They do not compound. ## List associations ```bash theme={null} curl "https://api.flexprice.io/v1/taxes/associations?entity_type=customer&entity_id=" \ -H "x-api-key: " ``` ## Update an association You can update `priority`, `auto_apply`, and `metadata` without deleting and recreating. ```bash theme={null} curl -X PUT https://api.flexprice.io/v1/taxes/associations/ \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "auto_apply": false }' ``` Setting `auto_apply: false` pauses the tax without removing the record. Set it back to `true` to re-enable. ## Common patterns **Exempt a specific customer from tax** Create no customer-level associations and ensure no tenant associations set to auto-apply are inherited. Customers with no resolvable active association generate invoices with zero tax. **Give one subscription different tax treatment** Use `tax_rate_overrides` at subscription creation. The customer's associations remain unchanged for their other subscriptions. **Stop a tax mid-lifecycle** Use the modification API with `action: "remove"`. Pass the `tax_association_id` from listing the subscription's associations. # Configure Tax Rates Source: https://docs.flexprice.io/docs/product-catalogue/taxes/configure Create and manage reusable tax rate definitions in flexprice. A tax rate is a reusable definition. You create it once with a unique `code`, then reference that `code` whenever you link it to a customer, subscription, or tenant. ## Create a tax rate ```bash theme={null} curl -X POST https://api.flexprice.io/v1/taxes/rates \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "name": "US Sales Tax", "code": "TAX_US_CA", "tax_rate_type": "percentage", "percentage_value": "8.25", "description": "California state sales tax" }' ``` **Response** ```json theme={null} { "id": "txr_01abc123", "name": "US Sales Tax", "code": "TAX_US_CA", "tax_rate_type": "percentage", "percentage_value": "8.25", "tax_rate_status": "active", "description": "California state sales tax", "created_at": "2025-01-15T10:00:00Z" } ``` ## Request fields | Field | Type | Required | Description | | ------------------ | -------------- | ------------------------------- | --------------------------------------------------------------------- | | `name` | string | Yes | Display name shown in invoices and the dashboard | | `code` | string | Yes | Unique identifier used in associations. **Immutable after creation.** | | `tax_rate_type` | string | Yes | `percentage` or `fixed` | | `percentage_value` | decimal string | When `tax_rate_type=percentage` | Rate as a percent, e.g. `"8.25"` for 8.25% | | `fixed_value` | decimal string | When `tax_rate_type=fixed` | Flat amount added per invoice, e.g. `"5.00"` | | `description` | string | No | Notes for your own reference | | `metadata` | object | No | Key-value pairs for your own tracking | The `code` field is immutable. Choose it carefully since it is the key used in all association requests. To rename a code, create a new tax rate and migrate your associations to it. ## Create a fixed-amount tax rate Use `tax_rate_type: fixed` when you need to add a flat fee to every invoice regardless of the subtotal, such as a regulatory levy. ```bash theme={null} curl -X POST https://api.flexprice.io/v1/taxes/rates \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "name": "Regulatory Fee", "code": "FEE_REG_US", "tax_rate_type": "fixed", "fixed_value": "5.00" }' ``` ## List tax rates ```bash theme={null} curl https://api.flexprice.io/v1/taxes/rates \ -H "x-api-key: " ``` ## Get a tax rate ```bash theme={null} curl https://api.flexprice.io/v1/taxes/rates/ \ -H "x-api-key: " ``` ## Update a tax rate You can update `name`, `description`, and `metadata`. The `code` and `tax_rate_type` fields are immutable. ```bash theme={null} curl -X PUT https://api.flexprice.io/v1/taxes/rates/ \ -H "x-api-key: " \ -H "Content-Type: application/json" \ -d '{ "description": "Updated for 2025 rate change" }' ``` ## Delete a tax rate A tax rate can only be deleted if it has no active associations and no applied records on existing invoices. To stop applying a tax without losing audit history, delete the association instead of the rate. ```bash theme={null} curl -X DELETE https://api.flexprice.io/v1/taxes/rates/ \ -H "x-api-key: " ``` **Error: rate has active associations** ```json theme={null} { "error": "tax_rate_in_use", "message": "Cannot delete a tax rate with active associations. Delete associations first." } ``` ## Next step Once you have a tax rate, [apply it to a customer or subscription](/docs/product-catalogue/taxes/apply). # Taxes Overview Source: https://docs.flexprice.io/docs/product-catalogue/taxes/overview How flexprice handles tax rates, associations, and automatic inheritance across customers and subscriptions. flexprice uses a two-object model for taxes: a **tax rate** (the reusable definition) and a **tax association** (the link between that rate and an entity). You define rates once and attach them to your tenant, customers, or individual subscriptions. Invoice generation picks up the right rates automatically and applies them to the post-discount subtotal. ## Two objects, one workflow **Tax Rate** The rate definition: its `tax_rate_type` (`percentage` or `fixed`), its value, and a unique `code` you reference everywhere else. **Tax Association** The link between a tax rate and an entity (`tenant`, `customer`, `subscription`, or `invoice`). The association controls whether the rate fires automatically on new invoices and at what priority when multiple rates apply. ## Inheritance chain flexprice resolves which tax rates apply to an invoice by walking this chain, from least specific to most specific: ``` Tenant-level associations (inherited when a customer has no associations of its own) Customer-level associations (inherited when a subscription has no tax_rate_overrides) Subscription-level associations <-- highest specificity, always wins ``` Setting `tax_rate_overrides` on a subscription replaces, not adds to, the customer's associations for that subscription's invoices. Omit `tax_rate_overrides` entirely to inherit from the customer. An invoice gets **no tax** if none of the three levels has an active association set to apply automatically. Verify your inheritance chain before going live. ## Where tax fits in invoice calculation Tax is always computed on the post-discount, post-credit balance: | Step | Value | | ------------------------------ | ------------------------------------------------------- | | Subtotal | Sum of all line items | | Line-item coupon discounts | Applied per line item | | Invoice-level coupon discounts | Applied to running subtotal | | Wallet / prepaid credits | Deducted | | **Taxable amount** | `MAX(subtotal - all_discounts, 0)` | | **Tax** | Sum of all matching tax rates applied to taxable amount | | **Invoice total** | `subtotal - discounts - wallet_credits + tax` | Tax is never charged on a negative base. If coupons eliminate the subtotal, tax is zero. ## Tax rate types | `tax_rate_type` | Use case | Value field | | --------------- | ---------------------------------------- | -------------------------------------------- | | `percentage` | Sales tax, VAT, GST | `percentage_value` (e.g. `"8.25"` for 8.25%) | | `fixed` | Flat regulatory fees, per-invoice levies | `fixed_value` | ## Multiple tax rates You can attach multiple tax rate associations to the same entity. Each rate is applied independently to the same taxable base (they do not compound). Use `priority` (lower number fires first) to control application order when ordering matters for reporting. ## Quick start 1. [Create a tax rate](/docs/product-catalogue/taxes/configure) with a unique `code` and a `percentage` or `fixed` value. 2. [Create a tax association](/docs/product-catalogue/taxes/apply) to link that rate to your tenant, a customer, or a subscription. 3. Generate an invoice. flexprice resolves applicable rates via the inheritance chain and applies them to the taxable amount automatically. # Manage API Keys Source: https://docs.flexprice.io/docs/rbac/api Generate and manage API keys with specific permissions API keys allow your services to authenticate with Flexprice. When you create an API key for a service account, it inherits that service account's permissions. ## How API Keys Work When you create an API key: 1. The key copies the service account's roles 2. The key's role are set and cannot be changed 3. Your service uses this key to make API calls 4. Flexprice checks the key's permissions for each request **Permissions Are Set at Creation** Once an API key is created, its permissions don't change. To get new permissions, create a new API key with required. ## Creating an API Key ### Via Dashboard Navigate to **Developers > API Keys** and click **Create** to open the API key creation dialog. Create API Key In the dialog: 1. **Name**: Give your API key a descriptive name 2. **Account Type**: Select between User Account or Service Account 3. **Mapped to Identity**: If Service Account is selected, choose which service account to use 4. **Expiration**: Set when the key should expire (or select Never) 5. Click **Create** The API key will be shown once - make sure to copy and save it securely. ### Via API - For Your Own Account Create an API key for yourself: ```bash cURL theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/secrets/api/keys \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "expires_at": "", "name": "", "type": "private_key" }' ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.cloud.flexprice.io/v1/secrets/api/keys', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '' }, body: JSON.stringify({ name: 'My Production Key', type: 'private_key' }) }); ``` ```python Python theme={null} import requests response = requests.post( 'https://api.cloud.flexprice.io/v1/secrets/api/keys', headers={ 'Content-Type': 'application/json', 'x-api-key': '' }, json={ 'name': 'My Production Key', 'type': 'private_key' } ) ``` ### For a Service Account Create an API key for a service account: ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/secrets/api/keys \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "expires_at": "", "name": "", "service_account_id": "", "type": "private_key" }' ``` ### Response ```json theme={null} { "id": "sec_xyz789", "name": "Event Service Production Key", "api_key": "sk_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "roles": ["event_ingestor"], "created_at": "2025-11-14T10:00:00Z" } ``` **Save Your API Key Now** The `api_key` value is only shown once, when you create it. Copy and store it securely immediately - you cannot retrieve it later. ## Storing API Keys Securely **✅ Good Practices:** * Store in environment variables * Use secret management services (AWS Secrets Manager, HashiCorp Vault) * Use Kubernetes Secrets for containerized apps * Store in your CI/CD platform's secret storage **❌ Bad Practices:** * Committing to Git repositories * Hardcoding in your application code * Sharing via email or messaging apps * Storing in plain text files ## Request Parameters A descriptive name to help you identify this key later. Examples: `"Production Event Service"`, `"Staging Analytics"`, `"Development Integration"` The ID of the service account to create the key for. If not provided, creates a key for your own account. ## Understanding Roles and Permissions ### Example 1: Event Ingestor Role **Service Account has**: `event_ingestor` role **API Key inherits**: `event_ingestor` role **What happens**: * ✅ Can send events * ❌ Cannot read events (403 Forbidden) ### Example 2: Multiple Roles **Service Account has**: Both `event_ingestor` and `event_reader` roles **API Key inherits**: Both roles **What happens**: * ✅ Can send events (from `event_ingestor`) * ✅ Can read events (from `event_reader`) ## Best Practices **Use Descriptive Names** Name your keys clearly: `"production-event-service"`, `"staging-analytics"`, `"dev-integration"` **Rotate Keys Regularly** Create new API keys and delete old ones every 90 days. **One Key Per Service** Create separate API keys for each service or integration, even if they use the same service account. ## Troubleshooting ### API Key Not Working **Symptom**: Getting "Unauthorized" errors when using the API key. **Possible Causes**: * API key format is incorrect * API key has been deleted * Using the wrong key for the environment (test vs. production) **Solution**: 1. Check if the key still exists 2. Make sure you're using the correct environment ### Getting 403 Forbidden Errors **Symptom**: API calls return "Forbidden" errors. **Possible Causes**: * The service account doesn't have the required role * The API key was created before roles were assigned * The role doesn't include the permission you need **Solution**: 1. Check what roles the API key has 2. Verify those roles include the permission you need 3. If roles were recently added, create a new API key with the service account associated with required roles ### Lost API Key **Symptom**: You didn't save the API key when you created it. **Solution**: API keys cannot be retrieved after creation. You need to: 1. Create a new API key for the same service account 2. Update your service with the new key 3. Delete the old key ## Related Resources Learn how to create service accounts → See all available roles and permissions → # Overview Source: https://docs.flexprice.io/docs/rbac/overview Control access to your Flexprice resources with role-based permissions Flexprice's Role-Based Access Control (RBAC) system allows you to create service accounts with specific permissions for automated services and integrations. Instead of sharing your main account credentials, create dedicated service accounts with only the access they need. ## What is RBAC? RBAC lets you create service accounts for your automated systems - like event ingestion services, analytics dashboards, or CI/CD pipelines - and assign each one specific roles that control what it can access. **Key Benefits:** * **Enhanced Security**: Limit what each service can access * **Controlled Access**: Assign specific permissions based on what each service needs to do * **Easy Management**: Create and revoke service account access without affecting your main account * **Clear Separation**: Different API keys for different purposes ## How It Works When you create a service account, you assign it one or more roles. Each role grants specific permissions to access certain resources. **Example Flow:** 1. Create a service account and assign it the `event_ingestor` role 2. Generate an API key for that service account 3. The API key inherits the `event_ingestor` permissions 4. Your service can now ingest events but cannot access customer data or billing information ## User Types ### Regular Users (User Accounts) Your standard user accounts for people. These accounts: * Can log in to the dashboard * Have full access to all Flexprice resources * API keys from user accounts have full access ### Service Accounts Automated accounts for your services and integrations. These accounts: * Cannot log in to the dashboard * Must have at least one role assigned * Access Flexprice only through API keys * API keys from service accounts have restricted access based on assigned roles ## Available Roles ### super\_admin For services that need unrestricted access to every Flexprice resource. **Can do:** * Perform any action on any resource (read, write, delete) **Cannot do:** * Nothing — this role grants `*` permissions on `*` resources **Use for:** Internal automation, migrations, or admin tooling that needs the same access as a user account. Treat the API key like a root credential. Only assign `super_admin` when no narrower role works. Rotate keys frequently and store them in a secret manager. ### event\_ingestor For services that send events to Flexprice. **Can do:** * Send events **Cannot do:** * Read events * Access any other resources **Use for:** Event ingestion services, usage tracking tools ### event\_reader For services that read event data. **Can do:** * Read events **Cannot do:** * Send events * Modify any data **Use for:** Analytics dashboards, reporting tools ## Real-World Use Cases ### Use Case 1: Event Ingestion Service **Scenario**: You have a microservice that sends usage events to Flexprice. **Solution**: Create a service account with the `event_ingestor` role and generate an API key. **What happens**: * ✅ Your service can send events successfully * ❌ If it tries to read events, it gets 403 Forbidden * ❌ It cannot access any other resources ### Use Case 2: Analytics Dashboard **Scenario**: Your analytics dashboard needs to read events for reporting. **Solution**: Create a service account with the `event_reader` role and generate an API key. **What happens**: * ✅ Your dashboard can read event data * ❌ If it tries to send events, it gets 403 Forbidden * ❌ It cannot modify any data ## Validation Rules **Service Accounts Require Roles** Every service account must have at least one role assigned. You cannot create a service account without specifying roles. ### Common Validation Errors **Error: No Roles Assigned** ```json theme={null} { "error": "Service accounts must have at least one role assigned" } ``` **Error: Invalid Role** ```json theme={null} { "error": "Invalid role: custom_role" } ``` ## Error Responses ### 403 Forbidden When a service account tries to access a resource it doesn't have permission for: ```json theme={null} { "error": "Forbidden", "message": "Insufficient permissions to write event" } ``` **What this means**: The API key doesn't have the required permission. You need to: 1. Check which role the service account has 2. Verify that role includes the permission you need 3. If needed, assign a different role to the service account 4. Generate a new API key (permissions are set when the key is created) ## Getting Started ### Step 1: Create a Service Account Choose the appropriate role for your use case and create a service account: ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/users \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "roles": [ "" ], "type": "user" }' ``` ### Step 2: Generate an API Key Create an API key for your service account: ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/secrets/api/keys \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "expires_at": "", "name": "", "service_account_id": "", "type": "private_key" }' ``` The API key is only shown once. Copy and store it securely immediately. ### Step 3: Use the API Key Use the API key in your service: ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/events \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "customer_id": "customer456", "event_id": "event123", "event_name": "api_request", "external_customer_id": "customer456", "properties": { "\"response_status\"": "200}", "{\"request_size\"": "100" }, "source": "api", "timestamp": "2024-03-20T15:04:05Z" }' ``` ## Best Practices **Use Specific Roles** Assign only the roles your service needs. Don't use broader permissions than necessary. **Rotate API Keys Regularly** Generate new API keys periodically (recommended: every 90 days) and delete old ones. **Store Keys Securely** Use environment variables or secret management systems. Never commit API keys to your code repository. ## Troubleshooting ### Getting 403 Errors **Symptom**: Your service is getting "Forbidden" errors when making API calls. **Possible Causes**: * The service account doesn't have the role that grants the required permission * The API key was created before you assigned roles to the service account * You're trying to access a resource that the assigned role doesn't cover **Solution**: 1. Check what roles the service account has 2. Verify those roles include the permission you need 3. If you recently added roles, create a new API key 4. Test with a simple request to verify permissions ### Cannot Create Service Account **Symptom**: Getting an error when trying to create a service account. **Possible Causes**: * Didn't specify any roles * Specified an invalid role name **Solution**: Make sure you're providing at least one valid role in the `roles` array. ## Next Steps Learn how to create service accounts → Learn how to generate and manage API keys → # Creating a Service Account Source: https://docs.flexprice.io/docs/rbac/service-accounts/create Create a service account with specific permissions for your automated services A service account is a special type of account for automated services, integrations, and scripts. Unlike your regular user account, service accounts are assigned specific roles that control what resources they can access in Flexprice. ## Why Use Service Accounts? Service accounts let you: * **Isolate access**: Each service gets its own account with only the permissions it needs * **Improve security**: If an API key is compromised, the damage is limited to that service's permissions * **Track usage**: See which service is making which API calls * **Manage easily**: Revoke access for one service without affecting others ## Creating a Service Account ### Choose the Right Role Before creating a service account, decide which role fits your use case: Flexprice currently supports following custom roles: | Role | Use When | | ---------------- | --------------------------------------------------------------------------------- | | `super_admin` | Your service needs full, unrestricted access (treat the key as a root credential) | | `event_ingestor` | Your service needs to send events to Flexprice | | `event_reader` | Your service needs to read event data | ### Via Dashboard Navigate to **Developers > Service Accounts** and click **Create** to open the service account creation dialog. Create Service Account In the dialog: 1. Select the roles you want to assign (Event Ingestor, Event Reader, or both) 2. Click **Create Service Account** The service account is created with the selected roles and you can now generate API keys for it. ### Via API ```bash cURL theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/users \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "type": "service_account", "name": "Event Ingestion Worker", "roles": ["event_ingestor"] }' ``` ```javascript JavaScript theme={null} const response = await fetch('https://api.cloud.flexprice.io/v1/users', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': '' }, body: JSON.stringify({ type: 'service_account', name: 'Event Ingestion Worker', roles: ['event_ingestor'] }) }); ``` ```python Python theme={null} import requests response = requests.post( 'https://api.cloud.flexprice.io/v1/users', headers={ 'Content-Type': 'application/json', 'x-api-key': '' }, json={ 'type': 'service_account', 'name': 'Event Ingestion Worker', 'roles': ['event_ingestor'] } ) ``` ### Response ```json theme={null} { "id": "usr_abc123", "type": "service_account", "roles": ["event_ingestor"], "created_at": "2025-11-14T10:00:00Z" } ``` Save the `id` from the response. You'll need it when creating API keys. ## Request Fields Must be set to `"service_account"`. List of roles to assign. Must include at least one role. Available roles: * `super_admin` - Full access to every resource * `event_ingestor` - Can send events * `event_reader` - Can read events Display name shown in the dashboard and audit logs. Helps you identify the service account later when listing or updating it. ## Validation Rules **At Least One Role Required** You must assign at least one role when creating a service account. Empty roles will return an error. ### Common Errors **No Roles Provided** ```json theme={null} { "error": "Service accounts must have at least one role assigned" } ``` **Invalid Role Name** ```json theme={null} { "error": "Invalid role: custom_role" } ``` ## Real-World Examples ### Example 1: Event Ingestion Service **Scenario**: You have a service that sends user events to Flexprice. **Create service account with**: * Role: `event_ingestor` **What happens**: * ✅ Service can send events * ❌ Service cannot read events (403 Forbidden) * ❌ Service cannot access other resources (403 Forbidden) ### Example 2: Analytics Dashboard **Scenario**: You have a dashboard that displays event data. **Create service account with**: * Role: `event_reader` **What happens**: * ✅ Dashboard can read events * ❌ Dashboard cannot send events (403 Forbidden) * ❌ Dashboard cannot modify anything (403 Forbidden) ## Assigning Multiple Roles You can assign both roles to give combined permissions: **Service account with both roles**: * Roles: `event_ingestor` and `event_reader` **What happens**: * ✅ Can send events (from `event_ingestor`) * ✅ Can read events (from `event_reader`) ## Updating a Service Account Rename a service account at any time. Only the display `name` can be changed; roles and type are immutable. ```bash theme={null} curl --request PUT \ --url https://api.cloud.flexprice.io/v1/users/{id} \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "name": "Event Ingestion Worker (prod)" }' ``` To rotate which roles a service account has, create a new service account with the new role set and migrate the API key over. ## Deleting a Service Account Archive a service account when it is no longer in use. Existing API keys for that account stop working immediately. ```bash theme={null} curl --request DELETE \ --url https://api.cloud.flexprice.io/v1/users/{id} \ --header 'x-api-key: ' ``` The endpoint returns `204 No Content` on success. The account is soft-deleted, so its history remains in audit logs. ## Best Practices **One Service Account Per Service** Create separate service accounts for each automated service or integration. **Assign Minimal Permissions** Only assign the roles your service actually needs. ## Troubleshooting ### Service Getting 403 Errors **Symptom**: Your service is getting "Forbidden" errors when making API calls. **Possible Causes**: 1. The service account doesn't have the required role 2. The API key was created before you assigned roles 3. The role doesn't include permission for that endpoint **Solution**: 1. Check what roles the service account has: `GET /v1/users/usr_abc123` 2. Verify those roles include the permission you need 3. If you recently added roles, create a new API key ### Cannot Create Service Account **Symptom**: Getting an error when creating the service account. **Possible Causes**: 1. Forgot to include `roles` in the request 2. Used an invalid role name 3. Specified `roles` as an empty array **Solution**: Make sure your request includes at least one valid role: ```json theme={null} { "type": "service_account", "roles": ["event_ingestor"] } ``` ## Next Steps Create API keys for your service account → # How to evolve pricing Source: https://docs.flexprice.io/docs/scenarios/how-to-evolve-pricing Use scenarios to experiment with packaging, rates, and pricing models before rolling them out Pricing should evolve in lockstep with your business. Market conditions shift, competitors adjust, and customer feedback reveals gaps in your current packaging. Scenarios let you model alternatives across all of Flexprice's pricing capabilities and make the best decision, with no end-customer consequences. ## Experimenting with packaging Plans (a pre-bundled set of prices) simplify how customers buy your product. But they can quickly become outdated as customer needs, willingness to pay, and market conditions shift. If your historical best-sellers no longer drive value, it's time to refresh your packaging. Create a [scenario](/docs/scenarios/overview) and start from your current plan prices so the hypothesis is grounded in what customers pay today. Add, remove, or modify usage-based or fixed fee prices to define your hypothesis. For a more significant packaging refresh or full plan redesign, start from scratch instead. Build a scenario from existing plan prices ## Updating price rates To simulate rate changes for existing prices, work from a copy of your current pricing so each hypothesis is comparable against the baseline. Copy your current pricing into a hypothesis to preserve your baseline for comparison. Edit the individual rate or change the pricing model on the hypothesis. Duplicate the hypothesis to model additional rate changes side by side. Update rates in a scenario hypothesis ## Simulating adjustments Whether you're planning the rollout of new pricing or understanding the impact of negotiated rates for enterprise customers, simulate the impact of [adjustments](/docs/product-catalogue/plans/price-overrides) (including discounts and spend maximums) before rolling them out. ## Transitioning to a credits-first model Run an impact analysis of a credits-first pricing model before making the switch. Create a hypothesis with a prepaid credit allocation to understand the revenue contribution of an upfront fixed commitment plus overage spend, relative to your current accrued pricing. Credits-first model in a scenario A credits-first model changes how and when revenue is recognized. Review how [prepaid and promotional credits](/docs/wallet/prepaid-and-promotional) work in Flexprice before modeling the transition. # How to launch new products Source: https://docs.flexprice.io/docs/scenarios/how-to-launch-new-products Use scenarios to de-risk first-time monetization and turn pricing into a growth lever Monetizing new experiences is all about value capture and alignment. Scenarios help you navigate first-time monetization or evolve your pricing as your product matures, without real-world billing risk. ## Early-stage products: transitioning from legacy pricing The shift from legacy subscription pricing to usage-first monetization requires careful risk management. If you've launched a new AI feature or agent, folding this functionality into the price of an existing seat-based subscription won't scale. As adoption grows, so do your costs. Introduce usage-first pricing via a scenario hypothesis. This lets you monitor product adoption and costs over time while building a business case for when and how to price a new product. Common starting points include: * **Standalone pricing**: charge for the new product directly to de-risk the rest of your offerings * **Hybrid pricing**: reduce fixed fees alongside usage-based pricing to lower the barrier to entry while maintaining repeatable revenue Reviewing a new product scenario ## Growth-stage products: finding your value metric For products entering the growth and scale stage, monetization is about aligning pricing with the value your product delivers. For AI builders especially, this means evaluating and transitioning across different metric types: * **Cost-centric**: pricing is directly tied to unit economics. In early stages, the cost of using your product is passed through to customers. * **Usage-first**: pricing is tied to overall adoption and usage. Typically commands a higher rate than cost-centric pricing. * **Value-based / outcome-based**: pricing is tied to outcomes and product value. When your customers win, you win. This commands the highest price point and encourages further product use. In Flexprice Scenarios, model each metric type as a separate hypothesis. Comparing results side by side surfaces the differences, implications, and tradeoffs of each pricing approach, without any real-world consequences. Build and compare pricing hypotheses ## Mature products: tuning rates to the market Even for established products, changing market conditions and competitive pressure require constant pricing refinement. Copy your existing pricing into a hypothesis to preserve a baseline for comparison. Edit the individual rate or change the pricing model on the hypothesis. Duplicate the hypothesis to model additional changes side by side. Tune rates in a scenario These tools let you quickly test new pricing hypotheses and experiment with your model, all grounded in real historical data. # Overview Source: https://docs.flexprice.io/docs/scenarios/overview Visualize the impact of pricing changes using your real historical data before making them live Scenarios let you model price changes in Flexprice before you make them, with none of the billing risk that comes with real-world experimentation. Define the price change you're considering, set your parameters, and Flexprice outputs forecasted revenue, customer impact, and churn guidance using your real historical data, so you can make an informed decision before going live. Because Flexprice sits at the intersection of your product usage and revenue data, Scenarios are useful for: * **Backtesting** to de-risk price changes before rolling them out * **Forecasting** for new product pricing * **Churn prediction** to identify which customers are most at risk of churning if prices change Scenarios overview ## Before you start To use Scenarios, make sure you're up and running with Flexprice. Specifically, you'll need to: * [**Ingest events**](/docs/event-ingestion/overview): gives Flexprice visibility into your product usage to power the scenario * [**Define a meter**](/docs/event-ingestion/creating-a-metered-feature): models the specific usage metric the price change applies to * [**Have a plan with subscriptions**](/docs/subscriptions/customers-create-subscription): defines the customer cohort your scenario will run over ## Creating a scenario Name your scenario for easy reference, set a historical time frame, and select a plan to run the scenario over. This determines the historical data used in your analysis. Define scenario parameters Aim for a window that provides sufficient, representative data. At least 30 to 90 days is recommended for meaningful results. The longer the window, the more visibility you have into customer behavior and seasonal patterns. A hypothesis represents a hypothetical price change you're considering. Flexprice supports two pricing types: * **Usage-based prices**: best for modeling prices that scale with variable events: tokens consumed, API calls made, minutes of audio processed. * **Fixed fee prices**: best for capturing upfront repeatable revenue, such as a platform fee or minimum spend. A scenario can include one or multiple hypotheses to compare against your baseline. Common setups include: * **Optimize the price point**: create a hypothesis, duplicate it, and assign different rates to compare * **Find your value metric**: create one hypothesis per metric candidate to compare them side by side * **Compare pricing strategies**: model standalone usage-based pricing vs. hybrid usage + fixed fee * **Compare pricing models**: evaluate unit pricing vs. tiered pricing on the same data We recommend up to 5 hypotheses per scenario. Beyond that, the results become harder to compare at a glance. Flexprice runs your scenario against real historical data. You'll receive a notification when results are ready to review. ## Acting on insights Scenario results are designed to help you decide whether and how to implement your price change. Key revenue and customer risk impacts are surfaced for consideration, replacing the manual spreadsheet alternative. ### Top-line insights The summary cards highlight the upside potential and downside risk associated with your price change across the customers in your scenario. Flexprice highlights: * The hypothesis with the **largest absolute revenue increase** * The hypothesis with the **lowest relative % change** to what customers currently pay When evaluating a price change, don't optimize purely for revenue uplift. Weigh the risk of price increases on your existing customers, especially your largest ones. Top-line scenario insights ### Revenue over time Monitoring how incremental revenue changes over time across hypotheses helps you: * Diagnose unexpected revenue variability * Identify seasonality impacts * Surface underlying product usage patterns worth considering Aim for a smooth, consistent revenue curve. Inconsistent spikes may suggest the price change is not aligned with your optimal monetization strategy. Revenue over time chart ### Impact concentration Understand where the impact of your price change is concentrated. A $5 increase affects a customer paying $1 very differently from one paying \$100. Aim for outcomes concentrated in the lower-right of the impact chart: the least relative % change to baseline revenue, with the greatest absolute revenue upside. Impact concentration chart ### Customer impact table Proactively identify which customers will be affected by your change. Get ahead of notifying your customer-facing teams before the change goes live. From this table, you can: * Review [historical subscriptions and invoices](/docs/subscriptions/view) to understand customer LTV * Create a [price override](/docs/product-catalogue/plans/price-overrides) to accommodate exceptions * Migrate affected customers to a new plan version while grandfathering marquee accounts on existing pricing Customer impact table ## Explore use cases # Settings Source: https://docs.flexprice.io/docs/settings/settings Configure subscription auto-cancellation, invoice numbering, and wallet balance alerts at the tenant and environment level. Settings in Flexprice control behavior for respective environment against tenant, providing complete isolation between different environments within and across tenants. All settings are scoped to per (tenant, environment) pair, validated on write, cached for one hour, and audited. **Benefits:** * **Tenant and environment isolation** — Each tenant and environment has its own settings; no cross-contamination * **Flexible updates** — Partial updates supported; only send the fields you want to change * **Automatic caching** — Settings are cached for one hour to reduce latency * **Audit trail** — All changes tracked with user and timestamp * **Validated on write** — Strict type checking and value constraints prevent invalid configuration ## Available Settings Flexprice supports three setting keys: * **Subscription Configuration** (`subscription_config`) — Controls subscription auto-cancellation for unpaid invoices * **Invoice Configuration** (`invoice_config`) — Controls invoice number generation and due date calculation * **Wallet Balance Alert Configuration** (`wallet_balance_alert_config`) — Wallet balance alert thresholds configurations per tenant per environment *** ## Subscription Configuration **Key:** `subscription_config` Controls subscription auto-cancellation for unpaid invoices. When enabled, subscriptions are automatically cancelled after grace period if invoices remain unpaid. ### Schema ```json theme={null} { "grace_period_days": number, "auto_cancellation_enabled": boolean } ``` * **`grace_period_days`** — Required for new settings; optional on update. Integer ≥ 1, no decimals. Number of days after invoice due date before auto-cancellation. * **`auto_cancellation_enabled`** — Optional; defaults to `false`. Boolean. Enable or disable auto-cancellation for this tenant and environment. ### Default Values When creating a new setting via API, defaults are: ```json theme={null} { "grace_period_days": 3, "auto_cancellation_enabled": false } ``` ### What Happens When Auto-Cancellation Is Enabled When you enable auto-cancellation and set a grace period: * **Subscriptions with overdue unpaid invoices** — If an invoice remains unpaid past its due date plus the grace period, the subscription is cancelled immediately (not at the end of the billing period). * **You get notified** — A `subscription.canceled` webhook is sent so you can sync your systems. * **Clean shutdown** — Future credit grants for that subscription are cancelled, and a final invoice is generated for any partial period used. * **Audit trail** — Cancellations are recorded with reason and metadata so you can see why and when a subscription was cancelled. ### Validation * **New settings:** `grace_period_days` required, integer ≥ 1, no decimals. `auto_cancellation_enabled` optional, boolean, default `false`. * **Updates:** Partial updates allowed; only provided fields validated; omitted fields unchanged. *** ## Invoice Configuration **Key:** `invoice_config` Controls invoice number generation (prefix, format, sequence, timezone) and due date calculation. ### Schema ```json theme={null} { "prefix": string, "format": string, "start_sequence": number, "timezone": string, "separator": string, "suffix_length": number, "due_date_days": number } ``` * **`prefix`** — Required for new settings; optional on update. Non-empty string, not only whitespace. Invoice number prefix (e.g. `"INV"`). * **`format`** — Required for new settings; optional on update. One of `YYYYMM`, `YYYYMMDD`, `YYMMDD`, `YY`, `YYYY`. Date format in invoice number. * **`start_sequence`** — Required for new settings; optional on update. Integer ≥ 0, no decimals. Starting sequence number. * **`timezone`** — Required for new settings; optional on update. Valid IANA timezone (e.g. `America/New_York`, `UTC`) or common abbreviation (e.g. `EST`, `GMT`). Timezone for date formatting. * **`separator`** — Required for new settings; optional on update. String (empty allowed). Character(s) between prefix, date, and sequence (e.g. `"-"`). * **`suffix_length`** — Required for new settings; optional on update. Integer 1–10. Number of digits for sequence (padded with zeros). * **`due_date_days`** — Optional; defaults to `1`. Integer ≥ 0, no decimals. Number of days after invoice creation to set due date. ### Default Values When creating a new setting via API, defaults are: ```json theme={null} { "prefix": "INV", "format": "YYYYMM", "start_sequence": 1, "timezone": "UTC", "separator": "-", "suffix_length": 5, "due_date_days": 1 } ``` ### What You Get Invoice numbers are generated in the format you configure: prefix, optional separator, date (based on your format and timezone), then a zero-padded sequence. Each tenant and environment has its own sequence, so numbers stay unique and predictable. The sequence resets according to the date format (e.g. monthly for `YYYYMM`, daily for `YYYYMMDD`). **Example with separator:** Configuration: `prefix: "INV"`, `format: "YYYYMM"`, `separator: "-"`, `suffix_length: 5`. Generated: `INV-202501-00001`, `INV-202501-00002`. **Example without separator:** Configuration: `prefix: "INV"`, `format: "YYYYMM"`, `separator: ""`, `suffix_length: 5`. Generated: `INV20250100001`, `INV20250100002`. ### Supported Timezones * **IANA names (recommended):** `America/New_York`, `Europe/London`, `Asia/Tokyo`, `UTC`. * **Common abbreviations:** `EST`, `CST`, `MST`, `PST`, `GMT`, `CET`, `EET`, `IST`, `JST`, `KST`, `AEST`, `AWST`, and more. ### Validation * **New settings:** All fields except `due_date_days` required; see constraints above. * **Updates:** Partial updates allowed; same rules for provided fields; omitted fields unchanged. *** ## Wallet Balance Alert Configuration **Key:** `wallet_balance_alert_config` Sets the **default** balance alert configuration for the tenant and environment. It applies only to wallets that do not have their own alert settings. Per-wallet alert configuration is done via [Wallet Sentinel Alerts](/docs/wallet/low-balance-alert) (or `alert_settings` on the wallet in the API). ### What This Setting Controls * **When enabled** — Wallets in this tenant and environment that have no per-wallet alert settings use this config as their default. Balance alerts run for those wallets using the thresholds you set here. * **When disabled** — Wallets that would otherwise use this default do not receive balance alerts until they have their own alert settings or you turn this setting back on. * **Per-wallet overrides** — Any wallet with its own `alert_settings` always uses those; this setting only applies to wallets without per-wallet config. Resolution order for a given wallet: Flexprice uses the wallet’s own alert settings if present; otherwise it uses this tenant-and-environment default. If the default is disabled, wallets without per-wallet settings do not receive balance alerts. ### Schema ```json theme={null} { "alert_enabled": boolean, "critical": { "threshold": string, "condition": "below" | "above" }, "warning": { "threshold": string, "condition": "below" | "above" }, "info": { "threshold": string, "condition": "below" | "above" } } ``` * **`alert_enabled`** — Required. Boolean. Turn alerts on or off at tenant and environment level. * **`critical`**, **`warning`**, **`info`** — Optional. Each has `threshold` (string, e.g. `"0"`, `"-5"`, `"10"`) and `condition` (`"below"` or `"above"`). ### Default Value When no custom value is stored (or after DELETE), the default is: ```json theme={null} { "critical": { "threshold": "0", "condition": "below" }, "alert_enabled": false } ``` ### Validation * If `alert_enabled` is `true`, at least one of `critical`, `warning`, or `info` must be present. * Threshold ordering: for `"below"`, critical \< warning \< info; for `"above"`, critical > warning > info (as applicable). ### Alert behavior (same as per-wallet alerts) Once a wallet is using alert config (either its own or this default), behavior matches [Wallet Sentinel Alerts](/docs/wallet/low-balance-alert): balance is monitored against the thresholds, **Info** / **Warning** / **Critical** are raised when thresholds are crossed, state is updated on the wallet, and webhooks are sent when alert state changes. ## API Endpoints ### Get Setting ``` GET /v1/settings/:key ``` **Parameters**: * `key` (path): Required. One of: `subscription_config`, `invoice_config` **Response**: ```json theme={null} { "value": { // Setting specific fields based on key type }, "tenant_id": "string", "environment_id": "string", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` **Error Responses**: * `404 Not Found`: Setting not found for tenant × environment * `400 Bad Request`: Invalid setting key * `403 Forbidden`: Missing tenant or environment context ### Create/Update Setting ``` PUT /v1/settings/:key ``` **Parameters**: * `key` (path): Required. One of: `subscription_config`, `invoice_config` **Request**: ```json theme={null} { "value": { // Setting specific fields based on key type } } ``` **Response**: ```json theme={null} { "value": { // Complete setting after operation }, "tenant_id": "string", "environment_id": "string", "created_at": "2024-01-01T00:00:00Z", "updated_at": "2024-01-01T00:00:00Z" } ``` **Error Responses**: * `400 Bad Request`: Invalid setting key or validation failed * `403 Forbidden`: Missing tenant or environment context **Behavior**: * Single endpoint handles both creation and updates * If setting doesn't exist for tenant × environment: * Creates new setting * All required fields must be provided * Default values applied for optional fields * If setting exists for tenant × environment: * Updates existing setting * Supports partial updates * Only provided fields are validated * Omitted fields retain their existing values * No defaults are applied ### Delete Setting ``` DELETE /v1/settings/:key ``` **Parameters**: * `key` (path): Required. One of: `subscription_config`, `invoice_config` **Response**: ```json theme={null} { "message": "Setting deleted successfully" } ``` **Error Responses**: * `404 Not Found`: Setting not found for tenant × environment * `400 Bad Request`: Invalid setting key * `403 Forbidden`: Missing tenant or environment context *** ## Use Cases ### Subscription Auto-Cancellation * **SaaS with trial plans** — Auto-cancel free trials after grace period if no payment method is added * **B2B billing** — Cancel subscriptions with overdue invoices after a grace period (e.g. 30 days) * **Compliance** — Automatically stop service for customers who haven't paid within the grace period ### Invoice Numbering * **Multi-region operations** — Use timezone to generate invoice numbers in the customer's local time * **Custom branding** — Set prefix and separator to match your invoice format (e.g. `ACME-202501-00001`) * **Sequence isolation** — Each tenant and environment has its own sequence; no conflicts ### Wallet Balance Alerts (Tenant and Environment Default) * **Enable alerts for all wallets** — Set tenant and environment default so new wallets inherit alert config * **Disable alerts globally** — Turn off async consumer for that environment by setting `alert_enabled: false` * **Fallback for wallets without config** — Wallets with no `alert_settings` use this default *** ## Best Practices 💡 **Send required fields on create** — When creating a new setting, include all required fields; defaults apply only on API create. 💡 **Use partial updates** — Send only the fields you want to change; omitted fields keep their existing values. 💡 **Prefer IANA timezones** — For `invoice_config`, use IANA names (e.g. `America/New_York`) instead of abbreviations for clarity and daylight saving time support. 💡 **Set a default for wallet balance alerts** — Use `wallet_balance_alert_config` to define default thresholds for all wallets in this tenant and environment that don’t have their own alert settings. Enable it so those wallets get balance alerts; disable or DELETE to turn alerts off for them. 💡 **Restrict who can change settings** — Limit setting changes to admin users; use tenant-scoped API keys and review audit logs. 💡 **Match grace period to your dunning process** — Set `grace_period_days` to align with how long you allow overdue invoices before cancelling (e.g. after how many reminder emails or days). 💡 **Monitor auto-cancellation outcomes** — Use `subscription.canceled` webhooks to see which subscriptions were cancelled and adjust grace period or dunning if needed. 💡 **Use invoice prefix and format for traceability** — A clear prefix and consistent date format in invoice numbers make it easier to match invoices to periods and environments. 💡 **Combine with per-wallet alerts** — Set `wallet_balance_alert_config` as the default, then override on specific wallets via [Wallet Sentinel Alerts](/docs/wallet/low-balance-alert) where you need different thresholds. *** ## Related Documentation * [Wallet Sentinel Alerts](/docs/wallet/low-balance-alert) — Configure per-wallet balance alerts * [Subscriptions](/docs/subscriptions/customers-create-subscription) — Manage customer subscriptions * [Invoices](/docs/invoices/overview) — Invoice lifecycle and payment tracking # Consolidated Billing Source: https://docs.flexprice.io/docs/subscriptions/billing-workflows/consolidated-billing One parent subscription covers multiple child customers. Usage from all children rolls up into a single invoice sent to the parent. ## Overview Consolidated billing lets an enterprise or holding company purchase one plan that covers multiple subsidiary teams or entities. Each child customer generates usage independently, but all usage aggregates under the parent subscription, producing a single invoice for the parent. `subscription_type` results: * Parent subscription: **`parent`** * Each child: **`inherited`** (auto-created skeleton, no line items) ## How it works ```mermaid theme={null} flowchart TB subgraph create [Creation] A[Create subscription for parent customer] B[Pass child external IDs in inheritance block] A --> B end subgraph skeletons [Auto-created by Flexprice] C[inherited subscription for child 1] D[inherited subscription for child 2] end subgraph usage [Usage ingestion] E[Events for child 1 customer] F[Events for child 2 customer] G[Rolled up to parent subscription] E --> G F --> G end subgraph invoice [Billing] H[Single invoice to parent customer] I[One line item per feature, usage aggregated] H --> I end B --> C B --> D G --> H ``` | Property | Where it lives | | -------------------------- | -------------------------------------------------------- | | Plan and line items | Parent subscription | | Usage tracking (billing) | Parent subscription (rolled up from all children) | | Usage tracking (analytics) | Per child customer (use `include_children` to aggregate) | | Invoice | Parent customer only | | Wallet | Parent customer | | Entitlements | Parent subscription | | Child subscriptions | Skeleton only: no line items, no independent invoice | ## When to use consolidated billing * **Enterprise with subsidiaries**: Global HQ purchases an enterprise plan. Regional divisions (APAC, EMEA, Americas) each generate usage independently. HQ receives one consolidated invoice. * **Holding company**: A holding company owns multiple brands. Each brand operates as a customer. Finance at HQ handles all invoices. * **Internal chargeback**: A company tracks usage per department for internal reporting, but the central IT team is invoiced. ## Prerequisites * Parent customer created in Flexprice * Each child customer created in Flexprice (with a known `external_id`) * A plan created in the product catalogue * Child customers must **not** already have an active `inherited` subscription under another parent On create, you cannot combine `inheritance.external_customer_ids_to_inherit_subscription` with `invoicing_customer_external_id` or `subscriptions_ids_for_grouped_invoicing` in the same request. ## Configure ### Step 1: Create customers Create the parent customer and each child customer through the Dashboard or API. Customer records have no hierarchy fields; they stay flat. ### Step 2: Create the parent subscription with child inheritance 1. Navigate to **Subscriptions** and click **Create Subscription** 2. Select the **parent customer** and choose a plan 3. In the **Customer Hierarchy** section, enter the external IDs of the child customers who will inherit this subscription 4. Click **Create Subscription** Subscription create form showing Customer Hierarchy section ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "external_customer_id": "ext-global-hq", "plan_id": "plan_enterprise_monthly", "currency": "usd", "billing_period": "month", "billing_period_count": 1, "start_date": "2026-06-01T00:00:00Z", "inheritance": { "external_customer_ids_to_inherit_subscription": [ "ext-apac-team", "ext-emea-team" ] } }' ``` Response (abbreviated): ```json theme={null} { "id": "sub_01parent", "subscription_type": "parent", "customer_id": "cus_hq", "status": "active" } ``` Flexprice automatically creates an `inherited` skeleton subscription for each child external ID. Verify by calling `GET /subscriptions` filtered by the child customer. ### Step 3: Verify Open the parent subscription's detail page. Child skeleton subscriptions appear under it. ## Post-creation changes You can add additional child customers to an existing parent subscription using the modify API. ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions/{parent_subscription_id}/modify/execute \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "inheritance", "inheritance_params": { "external_customer_ids_to_inherit_subscription": ["ext-latam-team"] } }' ``` Replace `{parent_subscription_id}` with the `id` of the parent subscription. You can preview the modify operation without committing changes using `POST /subscriptions/{id}/modify/preview` with the same request body. ## Analytics: per-child and aggregated Inherited children do **not** appear in billing usage summaries. All billable usage is attributed to the parent subscription. For analytics (non-billing), use `POST /events/analytics`. **Aggregate all children under the parent:** ```bash theme={null} curl -X POST https://api.flexprice.io/v1/events/analytics \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "external_customer_id": "ext-global-hq", "start_time": "2026-06-01T00:00:00Z", "end_time": "2026-06-30T23:59:59Z", "include_children": true }' ``` **Per-child usage breakdown:** ```bash theme={null} curl -X POST https://api.flexprice.io/v1/events/analytics \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "external_customer_id": "ext-apac-team", "start_time": "2026-06-01T00:00:00Z", "end_time": "2026-06-30T23:59:59Z" }' ``` `include_children: true` aggregates all inherited children's usage into the parent's response. Query each child's `external_customer_id` individually to see per-child contribution. [**Get customer usage summary**](/api-reference/customers/get-customer-usage-summary) for a child that only has an **inherited** subscription returns **no billable usage** on that child. This is expected. Confirm totals on the **parent** customer or use analytics as above. ## Validations and constraints **Child already has an inherited subscription.** A customer that already has an `inherited` subscription under another parent cannot be added as a child here. Error: `"customer already has an inherited subscription"`. Resolve: cancel the existing inherited subscription or use a different child customer. **Inherited subscriptions cannot be cancelled directly.** Calling cancel on an `inherited` subscription returns an error: `"inherited subscription cannot be cancelled directly"`. Cancel the **parent** subscription instead. All inherited children are cancelled automatically at the same time. **Cascade behavior.** When the parent subscription is paused, resumed, or cancelled, all inherited children are updated to match automatically. **Wallet balance.** The parent customer's wallet is used for all settlement. Inherited children have no independent wallet balance for this subscription. **Mutual exclusion.** The `inheritance.external_customer_ids_to_inherit_subscription` field cannot be combined with `invoicing_customer_external_id` or `subscriptions_ids_for_grouped_invoicing` in the same request. ## Frequently asked questions No. Currency is set on the parent subscription and applies to all invoices. Inherited children use the parent's currency. Plan changes apply to the parent subscription only. Inherited skeleton subscriptions keep routing usage events to the updated parent. Billable usage for inherited subscriptions is attributed to the **parent** subscription. A child with only an inherited subscription has no independent billing usage ledger. This is expected. Use `POST /events/analytics` for per-child breakdowns. ## Related workflows When children need their own subscriptions but a third party pays When separate subscriptions should merge into one invoice # Delegated Invoicing Source: https://docs.flexprice.io/docs/subscriptions/billing-workflows/delegated-invoicing The subscription belongs to one customer, but the invoice is sent to a different customer: the delegated payer. Entitlements, usage, and line items stay on the subscription owner. ## Overview Delegated invoicing lets you separate **who uses** a subscription from **who pays** for it. The subscription owner (child) keeps full ownership of their plan, line items, usage tracking, and entitlements. Every invoice is raised against a designated billing customer: the delegated payer. `subscription_type` result: **`standalone`** (with `invoicing_customer_id` set on the subscription record) Delegated invoicing does not create a new subscription type. The subscription owner's subscription is still `standalone`. What changes is the **invoicing customer ID** stored on the subscription, which redirects invoice generation and wallet drawdown to the delegated payer. ## How it works ```mermaid theme={null} flowchart LR A[Subscription owner usage] --> B[Subscription standalone] B --> C[Invoice generated] C --> D[Sent to delegated payer] D --> E[Payer wallet drawn down] ``` | Property | Where it lives | | ------------------- | ------------------------------------------ | | Plan and line items | Subscription owner's subscription | | Usage tracking | Subscription owner | | Entitlements | Subscription owner's subscription | | Invoice | **Delegated payer** | | Wallet | **Delegated payer** | | Tax | Based on delegated payer's billing details | ## When to use delegated invoicing * **Reseller model**: A reseller signs up 10 end customers. Each customer has their own starter plan and usage. The reseller receives and pays all invoices centrally. * **Central billing department**: A company's Finance team handles all vendor payments. Engineering, Marketing, and Sales each track their own usage, but invoices go to Finance. * **Managed service provider**: An MSP provisions software for their clients. Each client has their own subscription, but the MSP is billed for all of them. ## Prerequisites * Subscription owner (child) customer created in Flexprice * Delegated payer customer created in Flexprice with a known `external_id` * Delegated payer customer must have `status: published` (active) at the time the subscription is created * A plan created in the product catalogue On create, `invoicing_customer_external_id` cannot be combined with `external_customer_ids_to_inherit_subscription` or `subscriptions_ids_for_grouped_invoicing`. ## Configure 1. Navigate to **Subscriptions** and click **Create Subscription** 2. Select the **subscription owner** (end customer) as the customer 3. Choose a plan 4. In the **Invoice To** field, enter the delegated payer's external ID 5. Click **Create Subscription** ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "external_customer_id": "ext-end-customer", "plan_id": "plan_starter_monthly", "currency": "usd", "billing_period": "month", "billing_period_count": 1, "start_date": "2026-06-01T00:00:00Z", "inheritance": { "invoicing_customer_external_id": "ext-reseller" } }' ``` Response (abbreviated): ```json theme={null} { "id": "sub_01child", "subscription_type": "standalone", "customer_id": "cus_end_customer", "invoicing_customer_id": "cus_reseller", "status": "active" } ``` ## Post-creation changes The delegated payer is **immutable** after subscription creation. To change the invoicing customer, cancel the existing subscription and create a new one with the correct `invoicing_customer_external_id`. ## Analytics Analytics work the same as standalone. Query the subscription owner's `external_customer_id` directly. No special flags are needed. Usage events are ingested under the subscription owner, not the delegated payer. ## Validations and constraints **Invoicing customer must be active.** The delegated payer must have `status: published` at creation time. Error: `"invoicing customer is not active"`. Resolve: activate the delegated payer customer before creating the subscription. **Invoicing customer is immutable.** The `invoicing_customer_external_id` cannot be changed after the subscription is created. To reassign, cancel and recreate the subscription. **Subscription type.** The subscription owner's subscription will show `subscription_type: standalone`. This is correct. Delegated invoicing is not a separate type; it is a standalone subscription with an invoicing redirect. ## Related workflows When one parent contract should cover multiple customers with a single plan When separate subscriptions should merge into one invoice # Grouped Invoicing Source: https://docs.flexprice.io/docs/subscriptions/billing-workflows/grouped-invoicing Merge invoices from multiple separate subscriptions into a single consolidated invoice. Each subscription keeps its own line items, entitlements, and usage tracking. ## Overview Grouped invoicing lets a customer with multiple separate subscriptions receive a single invoice that combines all line items from every subscription in the group. Each subscription retains its own plan, line items, usage tracking, and entitlements. Only the invoice consolidation changes. `subscription_type` results: * Parent (invoice anchor): **`parent`** * Each grouped child subscription: **`grouped_invoicing`** A standalone subscription acting as the parent is **automatically promoted** to `parent` type when the first child is added. ## How it works ```mermaid theme={null} flowchart TB subgraph subs [Three separate subscriptions] A[sub_analytics grouped_invoicing] B[sub_storage grouped_invoicing] C[sub_parent parent] end subgraph invoicing [Invoice generation] D[All line items merged] E[Single invoice to parent customer] D --> E end A --> D B --> D C --> D ``` | Property | Where it lives | | ------------------- | --------------------------------------------------------------------- | | Plan and line items | Each child's own subscription | | Usage tracking | Each child's own subscription | | Entitlements | Each child's own subscription | | Invoice | **Single consolidated invoice** to the parent subscription's customer | | Wallet | Parent subscription's customer | ## When to use grouped invoicing * **Multi-product company**: A company uses analytics, storage, and compute as three separate Flexprice subscriptions. Finance wants a single monthly invoice instead of three. * **Department consolidation**: Different teams each have their own subscription. A central account manager wants one invoice per billing cycle. * **Simplify payment**: A customer manages multiple subscriptions independently but wants to reduce invoice processing overhead. ## Prerequisites Before adding a subscription to a grouped invoicing group, all of the following must be true: | Requirement | What to check | | ---------------------------- | ------------------------------------------------------------------------------------- | | Child subscription type | Must be `standalone` | | Child subscription status | Must be `active` or `trialing` | | Child has no existing parent | `parent_subscription_id` must be null | | Parent subscription type | Must be `parent` or `standalone` (auto-promoted on first child add) | | Parent subscription status | Must be `active` or `trialing` | | Billing period | Child and parent must have the **same** `billing_period` (for example both `month`) | | Billing period count | Child and parent must have the **same** `billing_period_count` (for example both `1`) | | Billing anchor | Child and parent must share the **same** day-of-month and time-of-day billing anchor | | Currency | Child and parent must use the **same** currency | | Start date | Child `start_date` must be **on or after** parent `start_date` | Flexprice validates all of these requirements before adding a subscription to a group. A single mismatch returns a validation error with a specific hint. Check the error's `hint` field for which constraint failed. ## Configure ### Option A: Create a parent subscription and group existing subscriptions at creation time Use this when you already have standalone subscriptions that you want to group under a new parent. ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "external_customer_id": "ext-acme-corp", "plan_id": "plan_base_monthly", "currency": "usd", "billing_period": "month", "billing_period_count": 1, "start_date": "2026-06-01T00:00:00Z", "inheritance": { "subscriptions_ids_for_grouped_invoicing": [ "sub_analytics", "sub_storage" ] } }' ``` This creates a new `parent` subscription and converts `sub_analytics` and `sub_storage` from `standalone` to `grouped_invoicing` type. Response (abbreviated): ```json theme={null} { "id": "sub_01parent", "subscription_type": "parent", "customer_id": "cus_acme", "status": "active" } ``` At creation, `subscriptions_ids_for_grouped_invoicing` cannot be combined with `external_customer_ids_to_inherit_subscription`, `invoicing_customer_external_id`, or `parent_subscription_id` in the same request. ### Option B: Add a subscription to an existing group Use this when the parent already exists and you want to add more subscriptions to the group. Preview before executing to confirm all constraints will pass: ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions/{parent_subscription_id}/modify/preview \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "grouped_invoicing", "grouped_invoicing_params": { "action": "add", "parent_subscription_id": "{parent_subscription_id}", "child_subscription_ids": ["sub_compute"] } }' ``` ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions/{parent_subscription_id}/modify/execute \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "grouped_invoicing", "grouped_invoicing_params": { "action": "add", "parent_subscription_id": "{parent_subscription_id}", "child_subscription_ids": ["sub_compute"] } }' ``` Replace `{parent_subscription_id}` with the parent subscription's `id`. The same ID goes in both the URL and `grouped_invoicing_params.parent_subscription_id`. On success, `sub_compute` is converted from `standalone` to `grouped_invoicing` and linked to the parent. ### Option C: Remove a subscription from the group ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions/{parent_subscription_id}/modify/execute \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "type": "grouped_invoicing", "grouped_invoicing_params": { "action": "remove", "child_subscription_ids": ["sub_compute"] } }' ``` On success, `sub_compute` reverts from `grouped_invoicing` back to `standalone` and its `parent_subscription_id` is cleared. ## Post-creation changes Adding or removing group membership is done with the modify API (`Option B` and `Option C` above). There is no separate PATCH on the subscription resource for grouped invoicing membership beyond these flows. ## Timing behavior **Adding a subscription to a group** takes effect at the **next billing period boundary**. For the current period, any advance charges on the child have already been invoiced independently. Only usage accrued after the period rolls over appears on the consolidated parent invoice. **Removing a subscription from a group** applies to the **entire current billing period**. The child's invoice for the full current period (all usage and charges) is raised directly against the child customer's subscription, regardless of when during the period the removal occurred. ## Analytics Each subscription in the group has its own usage tracking. Query each subscription owner's `external_customer_id` individually. There is no `include_children` flag for grouped invoicing; that flag is for consolidated billing and inherited subscriptions only. ## Validations and constraints **Billing period, billing period count, billing anchor, and currency must all match exactly.** The billing anchor comparison uses the day-of-month plus time-of-day (hour:minute:second). Year and month are ignored, so a child added later in the year is accepted when its intra-cycle cadence aligns. **Cannot combine with consolidated billing at creation.** `subscriptions_ids_for_grouped_invoicing` cannot be used in the same request as `external_customer_ids_to_inherit_subscription`, `invoicing_customer_external_id`, or `parent_subscription_id`. **Standalone parent auto-promotion.** If the parent subscription is currently `standalone`, it is automatically promoted to `parent` type when the first child is added via the modify API. No manual type change is needed. ## Frequently asked questions Yes. Use `POST /subscriptions/{id}/modify/preview` with `type: "grouped_invoicing"` and `action: "add"`. The preview validates constraints and returns what would change without making writes. The cancelled child generates its own final invoice for the current period. The remaining children and the parent continue their normal billing cycle. No. A subscription has one type. Grouped invoicing and consolidated billing use different mechanisms and cannot be mixed on the same parent subscription. ## Related workflows When one parent plan should cover multiple customers When a third party should receive the invoice for a standalone subscription # Subscription Billing Workflows Source: https://docs.flexprice.io/docs/subscriptions/billing-workflows/overview Choose the billing workflow that matches your business model. Each workflow controls who owns the subscription, who receives the invoice, and how usage is tracked. ## Which workflow do I need? Every Flexprice subscription has a `subscription_type` that controls billing ownership. Use the table below to pick the right workflow before you start. | Workflow | Your situation | Who gets invoiced | `subscription_type` | | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ | ------------------------------ | ---------------------------------------------------- | | [Standalone](/docs/subscriptions/billing-workflows/standalone) | Each customer is independent: their own plan, invoice, and wallet | Subscription owner | `standalone` | | [Consolidated Billing](/docs/subscriptions/billing-workflows/consolidated-billing) | One parent contract covers multiple child entities; one invoice to HQ | Parent customer | `parent` (HQ) + `inherited` (children, auto-created) | | [Delegated Invoicing](/docs/subscriptions/billing-workflows/delegated-invoicing) | Each customer has their own subscription, but a third party pays all the bills | Delegated payer | `standalone` with invoicing redirect | | [Grouped Invoicing](/docs/subscriptions/billing-workflows/grouped-invoicing) | Multiple separate subscriptions for the same company; Finance wants a single invoice | Parent subscription's customer | `grouped_invoicing` (children) + `parent` | If none of these match, start with **Standalone**. It is the default and works for the majority of direct B2B and B2C billing scenarios. ## Key concept: hierarchy lives on subscriptions, not customers Flexprice keeps customer records flat. There are no parent or child fields on a customer. All billing relationships are configured on the **subscription**. This means: * You can mix billing workflows per customer without restructuring your customer data * Switching a customer from standalone to grouped invoicing is a subscription operation, not a customer edit * Two customers can be related for billing purposes while remaining independent entities in your system ## Workflows at a glance Default. One customer, one subscription, one invoice. No configuration needed. Enterprise HQ buys one plan. Subsidiaries generate usage. One consolidated invoice. Reseller model. Each end customer has their own subscription. One party pays all invoices. Multi-product company. Three separate subscriptions. Finance wants one monthly invoice. ## Mutual exclusion The four `inheritance` fields on Create Subscription are mutually exclusive. You can only specify one billing relationship per subscription: | Field | Cannot be combined with | | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | `external_customer_ids_to_inherit_subscription` | `invoicing_customer_external_id`, `subscriptions_ids_for_grouped_invoicing`, `parent_subscription_id` | | `invoicing_customer_external_id` | `external_customer_ids_to_inherit_subscription`, `subscriptions_ids_for_grouped_invoicing` | | `subscriptions_ids_for_grouped_invoicing` | `external_customer_ids_to_inherit_subscription`, `invoicing_customer_external_id`, `parent_subscription_id` | | `parent_subscription_id` | `external_customer_ids_to_inherit_subscription`, `subscriptions_ids_for_grouped_invoicing` | # Standalone Billing Source: https://docs.flexprice.io/docs/subscriptions/billing-workflows/standalone The default subscription workflow. Each customer has their own independent subscription, invoice, and wallet. No hierarchy configuration required. ## Overview Standalone is the default `subscription_type` in Flexprice. Every subscription is standalone unless you explicitly configure one of the other billing workflows. Use it whenever customers are fully independent: each has their own plan, usage ledger, invoice, and wallet. `subscription_type` result: **`standalone`** ## How it works ```mermaid theme={null} flowchart LR A[Customer] --> B[Subscription] B --> C[Usage events] B --> D[Invoice to customer] B --> E[Wallet drawdown] ``` Each subscription is self-contained: | Property | Where it lives | | ------------------- | --------------------------------- | | Plan and line items | Subscription | | Usage tracking | Customer | | Invoice | Customer (the subscription owner) | | Wallet | Customer | | Entitlements | Subscription | ## When to use standalone * **Direct SaaS**: Your customers sign up directly. Each company or user is an independent billing entity with no relationship to other customers. * **Multi-product**: One customer uses several of your products, each as a separate subscription. Each subscription invoices independently. * **Default**: You are not sure which workflow to use yet. Start here and migrate later. ## Prerequisites * A customer created in Flexprice (`POST /customers` or Dashboard) * A plan created in the product catalogue ## Configure 1. Navigate to **Subscriptions** and click **Create Subscription** 2. Select the customer 3. Choose a plan 4. Set start date, billing period, and any overrides 5. Click **Create Subscription** No additional configuration is needed. A subscription created without any `inheritance` fields is automatically standalone. ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "external_customer_id": "cust-acme", "plan_id": "plan_enterprise_monthly", "currency": "usd", "billing_period": "month", "billing_period_count": 1, "start_date": "2026-06-01T00:00:00Z" }' ``` Response (abbreviated): ```json theme={null} { "id": "sub_01abc", "subscription_type": "standalone", "customer_id": "cus_acme", "status": "active" } ``` ## Post-creation changes Standalone subscriptions support the standard subscription lifecycle and modification APIs (plan changes, pauses, overrides, and so on). There is no hierarchy or invoicing redirect to update. If you later need consolidated billing, delegated invoicing, or grouped invoicing, use the workflows in those guides and create or modify subscriptions accordingly. ## Analytics Usage analytics behave like any normal subscription. Call [**Get usage analytics**](/api-reference/events/get-usage-analytics) (`POST /events/analytics`) with the subscriber's `external_customer_id`. You do not need `include_children` (that flag applies to consolidated billing rollups only). ## Validations and constraints A customer that already has an **inherited** subscription (from a consolidated billing parent) cannot create a standalone subscription. If you need a standalone subscription for such a customer, remove them from the parent hierarchy first. ## Related workflows When one parent contract should cover multiple customers When a third party should receive the invoice When separate subscriptions should merge into one invoice # Commitment Source: https://docs.flexprice.io/docs/subscriptions/commitment/overview Commitment-based pricing lets a customer guarantee a minimum spend or quantity per billing period while billing actual usage above that floor at a configurable overage rate Commitment-based pricing is a billing model that lets customers commit to a minimum spend or quantity for each billing period. It gives the business predictable revenue and lets the customer pay for actual usage beyond the commitment at a defined overage rate. A commitment is configured on a subscription line item and, by default, applies across the entire billing period. To vary the commitment by time of day — for example, higher rates during business hours and lower rates overnight — use [time-of-day buckets](/docs/subscriptions/commitment/time-of-day-buckets). ## How Commitment Works When a commitment is configured on a subscription line item: 1. **Commitment amount** — The minimum the customer commits to per billing period (e.g. \$1,000/month). This guarantees the revenue floor. 2. **Overage factor** — A multiplier applied to usage cost above the commitment (e.g. 1.5x). When usage exceeds the commitment, the excess is billed at the overage rate. 3. **Usage calculation**: * If total usage cost ≤ commitment: all usage is charged at the base rate. * If total usage cost > commitment: the commitment portion is charged at the base rate, and the excess is charged at `base_rate × overage_factor`. ## Steps to Add a Commitment 1. Click **Add Subscription**. 2. Scroll to the **Commitment & Overage** section. 3. Set the commitment amount and overage factor. Defining Commitment ## Understanding True-Up True-up ensures the customer pays the full commitment even when actual usage falls short. When true-up is enabled and usage at period end is below the commitment, the shortfall is billed as a separate true-up line item. ### How True-Up Works 1. **During the billing period** — Flexprice tracks usage and computes the running usage cost. 2. **At invoice generation**: * Usage cost \< commitment AND true-up enabled → a true-up line item is added for the shortfall. * Usage cost ≥ commitment → standard commitment-and-overage logic applies; no true-up is needed. ### Steps to Add True-Up 1. Click **Add Subscription**. 2. Scroll to the **Commitment & Overage** section. 3. Set the commitment amount and overage factor. 4. Enable true-up using the toggle. Enabling True-Up ## Time-of-Day Commitment Buckets A line-item commitment applies for all 24 hours of the day. If you need different commitments and prices for different parts of the day — for example, reserved capacity 09:00–17:00 UTC and standard pricing outside — configure [time-of-day buckets](/docs/subscriptions/commitment/time-of-day-buckets) on the line item. # Time-of-Day Commitment Buckets Source: https://docs.flexprice.io/docs/subscriptions/commitment/time-of-day-buckets Time-of-day buckets override a line item's commitment, price, and overage for specific hour ranges within a UTC day A line-item [commitment](/docs/subscriptions/commitment/overview) applies for the full 24 hours of every day in the billing period. Time-of-day buckets let you override that commitment for specific hour ranges — each bucket carries its own price, commitment value, overage factor, and true-up flag. Usage that falls inside a bucket is billed under the bucket's terms; usage that falls outside every bucket continues to be billed under the line item's all-day commitment. **When to use buckets:** * **Peak / off-peak pricing** — Higher commitment and rate during business hours, lower outside. * **Reserved windows** — Reserved capacity at a guaranteed rate inside a fixed window, standard pricing elsewhere. * **Tiered SLAs** — Different minimum spends for production hours versus maintenance windows. Peak hours bucket layered on top of a full-day reserved commitment, with separate overage prices inside and outside the bucket The diagram shows four pricing layers in a single day, each mapped to a configuration field on the line item: | Layer | Time | Configured on | Behavior | | ----------------------------------- | ---------------------------- | ---------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------- | | **Reserved 10 units, Price A** | 00:00–23:59 | Line item (`commitment_value: 10`, `commitment_true_up_enabled: true`) | The line-item commitment reserves 10 units across the full day. True-up bills the full 10 units even when usage is lower. | | **Reserved 10 more units, Price C** | 07:00–17:00 | Bucket (`commitment_value: 10`, `price` = Price C) | The bucket reserves an additional 10 units at peak time. Inside the bucket window the customer has 20 reserved units total. | | **Overage 25 units, Price B** | 00:00–07:00 (outside bucket) | Line item base price | Usage above the line-item commitment outside any bucket is billed at the line-item base price. | | **Overage 10 units, Price D** | 07:00–17:00 (inside bucket) | Bucket base price + `overage_factor` | Usage above the bucket commitment inside the bucket is billed at the bucket's price multiplied by its overage factor. | ## How Buckets Override the Line Item A subscription line item carries a base price and an optional base commitment that apply across the entire billing period. A bucket attaches to that line item and overrides three things — but only for usage whose timestamp falls inside the bucket's `[start, end)` range: | Field | Inside a bucket | Outside every bucket | | ---------------- | ------------------------ | -------------------------------------- | | Price | Bucket price | Line-item base price | | Commitment value | Bucket commitment | Line-item commitment | | Overage factor | Bucket overage factor | Line-item overage factor | | True-up | Bucket `true_up_enabled` | Line-item `commitment_true_up_enabled` | You can attach as many buckets as you need to one line item. Buckets are stored in the order they are provided and matched at billing time by checking each window's start timestamp against bucket ranges. All bucket times are interpreted in **UTC**. Convert your local peak hours to UTC before configuring buckets. ### Example scenario A line item meters API calls with: * Base price: \$1.00 per call * Base commitment: 10 reserved units per window, true-up enabled (Price A) The customer adds a peak bucket from 07:00 to 17:00 UTC reserving 10 more units at Price C, and configures overage prices for both regions of the day (Price B outside the bucket, Price D inside). The resulting billing surface looks like this within a single day: | UTC time | Price applied | Commitment in effect | Charged for | | ----------- | ------------------------------------------------------------------------- | -------------------- | -------------------------------------------------------- | | 00:00–07:00 | Price A (reserved) + Price B (overage) | 10 units, true-up on | Reserved 10 units + 25 overage units at Price B | | 07:00–17:00 | Price A (reserved) + Price C (bucket reserved) + Price D (bucket overage) | 10 + 10 = 20 units | Reserved 10 + bucket-reserved 10 + 10 overage at Price D | | 17:00–23:59 | Price A (reserved) + Price B (overage) | 10 units, true-up on | Reserved 10 units, overage at Price B if exceeded | The line item's reserved 10 units (Price A) and true-up apply across the full day. The bucket layers an additional 10 reserved units at Price C only during 07:00–17:00, with its own overage at Price D for usage exceeding the bucket commitment. ## Constraints ### 1. The meter must be bucketed A bucket can only be attached to a line item whose meter has an aggregation window — a "bucketed" meter that applies commitment per window rather than once per billing cycle. The line item must have `commitment_windowed: true`. Attaching `commitment_time_buckets` to a line item with `commitment_windowed: false` returns a validation error: `commitment_time_buckets requires commitment_windowed=true`. ### 2. Bucket duration must be at least one meter window If the meter aggregates per hour, a bucket cannot be shorter than 1 hour. A bucket that spans less than one full meter window cannot accumulate a complete usage measurement. ### 3. Bucket duration must be an exact multiple of the meter window `end - start` must be divisible by the meter's window size. If the meter window is `HOUR`, the bucket `12:00 → 13:30` is rejected: the 90-minute duration leaves 30 minutes of a second meter window unaccounted for. | Meter window | Bucket `[start, end)` | Duration | Valid? | | -------------- | --------------------- | -------- | ---------------------------------- | | HOUR (60 min) | 09:00 → 10:00 | 60 min | Yes (1× window) | | HOUR (60 min) | 09:00 → 12:00 | 180 min | Yes (3× window) | | HOUR (60 min) | 09:00 → 10:30 | 90 min | No — not a multiple | | HOUR (60 min) | 09:30 → 10:30 | 60 min | No — start not aligned to the hour | | 15-min | 09:00 → 09:45 | 45 min | Yes (3× window) | | DAY (1440 min) | 00:00 → 24:00 | 1440 min | Yes (1× window) | The bucket `start` must also align to the meter window grid — for an hourly meter, the start minute-of-day must be divisible by 60. ### 4. Buckets cannot overlap Two buckets on the same line item cannot share any minute of the day. Adjacent buckets are allowed because ranges are half-open `[start, end)`: * `[09:00, 12:00)` and `[12:00, 17:00)` → **valid** (adjacent, no shared minute). * `[09:00, 12:00)` and `[11:00, 14:00)` → **invalid** (overlap on 11:00–12:00). If a use case genuinely needs different rates for the same period (for example, a tiered surcharge that stacks on top of a base bucket), model that as **slab pricing within a single bucket** and register it as one window — not as overlapping buckets. ### 5. Outside a bucket, the line-item commitment continues to apply Buckets are an override, not a replacement. Any usage window whose start timestamp falls outside every configured bucket is billed at the line item's base price and base commitment, with the line item's overage factor and true-up flag. If the line item has no base commitment, out-of-bucket usage is billed at the base rate only. ### Midnight-wrapping ranges A bucket can wrap midnight. `{ "start": { "hour": 22, "minute": 0 }, "end": { "hour": 6, "minute": 0 } }` covers 22:00–23:59 and 00:00–05:59 in UTC. The `24:00` end value is allowed only with `minute: 0` and represents end-of-day. ## Bucket Fields | Field | Description | | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `start` | `{ "hour": 0-24, "minute": 0-59 }`. The first minute the bucket applies. | | `end` | `{ "hour": 0-24, "minute": 0-59 }`. The first minute the bucket no longer applies. `24:00` is the only allowed end-of-day value. | | `price` | Inline subscription-scoped price. Required when creating a new bucket; omit on updates when keeping the existing price. | | `id` | Server-assigned bucket ID (`cmt_bkt_…`). Provide it on updates to keep the existing bucket and its price. | | `commitment_type` | `amount` or `quantity`. Matches the line-item commitment type. | | `commitment_value` | Minimum spend (when `commitment_type` is `amount`) or minimum quantity (when `commitment_type` is `quantity`) for usage windows inside the bucket. Must be > 0. | | `overage_factor` | Multiplier applied to usage above the bucket commitment. Must be ≥ 1.0. | | `true_up_enabled` | When `true`, the shortfall against `commitment_value` is billed at the last window of the commitment period. | ## Example: Peak and Off-Peak Commitment The line item meters API calls with an hourly window. Peak hours 09:00–17:00 UTC carry a higher reserved commitment and rate; off-peak hours carry a lower commitment, a discounted price, and true-up. ```json theme={null} { "commitment_type": "amount", "commitment_windowed": true, "commitment_duration": "DAY", "commitment_time_buckets": [ { "start": { "hour": 9, "minute": 0 }, "end": { "hour": 17, "minute": 0 }, "commitment_type": "amount", "commitment_value": "500.00", "overage_factor": "1.5", "true_up_enabled": false, "price": { "type": "USAGE", "billing_model": "FLAT_FEE", "billing_period": "DAY", "billing_period_count": 1, "invoice_cadence": "ARREAR", "amount": "0.10" } }, { "start": { "hour": 17, "minute": 0 }, "end": { "hour": 9, "minute": 0 }, "commitment_type": "amount", "commitment_value": "100.00", "overage_factor": "1.2", "true_up_enabled": true, "price": { "type": "USAGE", "billing_model": "FLAT_FEE", "billing_period": "DAY", "billing_period_count": 1, "invoice_cadence": "ARREAR", "amount": "0.04" } } ] } ``` In this configuration the second bucket wraps midnight: `[17:00, 09:00)` covers 17:00–23:59 and 00:00–08:59 UTC. Together the two buckets tile the full day, so every usage window matches exactly one bucket and the line-item base commitment is never consulted. ### Billing walk-through With an hourly meter and the configuration above, consider three windows in a single day: | Window start (UTC) | Calls | Bucket matched | Bucket price | Bucket commitment | Charge | | ------------------ | ----- | -------------- | ------------ | ----------------- | ---------------------------------------------- | | 09:00 | 6,000 | Peak | \$0.10 | \$500 | $500 + ($600 − $500) × 1.5 = $650 | | 14:00 | 5,000 | Peak | \$0.10 | \$500 | \$500 (usage exactly meets commitment) | | 23:00 | 1,000 | Night | \$0.04 | \$100 | $100 via true-up ($40 actual + \$60 shortfall) | The billing engine picks the bucket whose `[start, end)` range contains the window start timestamp, then runs the standard commitment/overage/true-up calculation against that bucket's values. ## Updating Buckets On `PATCH` of a subscription line item, the `commitment_time_buckets` array replaces the existing array: * **Keep an existing bucket** — include its `id` and omit `price`. Commitment fields are read from the request, so you can change `commitment_value`, `overage_factor`, or `true_up_enabled` while keeping the original price. * **Add a new bucket** — omit `id` and include an inline `price`. The server creates a subscription-scoped price and assigns a bucket ID. * **Remove all buckets** — send `"commitment_time_buckets": []`. Omitting the field entirely keeps the existing buckets unchanged. A single bucket entry cannot include both `id` and `price`. Provide `id` to reuse an existing bucket's price, or provide `price` to create a new bucket. ## Validation Errors | Condition | Error | | ---------------------------------------------------------------- | ---------------------------------------------------------------------------------- | | Buckets exist on a non-windowed line item | `commitment_time_buckets requires commitment_windowed=true` | | Buckets exist but the meter has no aggregation window | `buckets require a windowed meter` | | Meter window is larger than a day | `meter window must be <= 1 day when using buckets` | | `end - start` is not a multiple of the meter window | `bucket duration must be a multiple of the meter window` | | `start` is not aligned to the meter window grid | `bucket start alignment error: start must be on the meter window grid` | | Two buckets share any minute of the day | `buckets overlap` | | `start` equals `end` | `bucket start must differ from end` | | `commitment_value` ≤ 0 | `commitment_value must be > 0` | | `overage_factor` is missing or \< 1.0 | `overage_factor must be at least 1.0` | | Buckets combined with a cumulative subscription-level commitment | `per-bucket commitment cannot be combined with cumulative subscription commitment` | # Customer Hierarchy Source: https://docs.flexprice.io/docs/subscriptions/customer-hierarchy Configure billing relationships at the subscription level so you can separate who uses your product from who pays for it. This page covers consolidated billing and delegated invoicing. Flexprice now has a dedicated **Billing Workflows** section with full coverage of all four workflows, including grouped invoicing. * [Overview: Which workflow do I need?](/docs/subscriptions/billing-workflows/overview) * [Standalone](/docs/subscriptions/billing-workflows/standalone) * [Consolidated Billing](/docs/subscriptions/billing-workflows/consolidated-billing) * [Delegated Invoicing](/docs/subscriptions/billing-workflows/delegated-invoicing) * [Grouped Invoicing](/docs/subscriptions/billing-workflows/grouped-invoicing) **Customer = identity, Subscription = contract, Hierarchy = subscription configuration** ## Overview **Customer Hierarchy** is an enterprise feature for configuring billing relationships on each subscription. Flexprice keeps **customers** flat (no parent fields on customer records). All hierarchy behavior lives on the **subscription**, so you can mix models per contract without restructuring customer data. With Customer Hierarchy you can: * Send invoices to a different customer than the one using the service (**delegated payer**) * Cover many customers with one parent subscription and roll up usage into a single invoice (**consolidated subscription**) * Change how a subscription bills without editing customer records ## Two ways to use Customer Hierarchy Customer Hierarchy supports two independent patterns. Pick one based on who owns the plan and line items: | | Consolidated subscription | Delegated payer | | ---------------------------------- | --------------------------------------------------------------------- | -------------------------------------------------------------------------------- | | **Plan & line items** | Owned by the parent subscription | Owned by the child's subscription | | **Billing, usage summary, wallet** | **Parent** customer (rolled up from all inherited children) | **Child** subscription; invoices settle against the **delegated payer's** wallet | | **Usage events & identity** | Still associated with **child** customers for ingestion and analytics | Child customer (their own subscription) | | **Invoice goes to** | Parent customer | A specified billing customer (bill-to) | | **Best for** | Enterprise: one plan, many subsidiaries | Reseller: each customer has their own plan, a central payer | | **API field** | `inheritance.external_customer_ids_to_inherit_subscription` | `inheritance.invoicing_customer_external_id` | These two modes are mutually exclusive. You cannot combine `external_customer_ids_to_inherit_subscription` and `invoicing_customer_external_id` in the same subscription. ## How it works Every subscription has a `subscription_type` that describes its role in a hierarchy: | Type | Description | | ------------ | ------------------------------------------------------------------------------------------------------------------------------------------ | | `standalone` | A regular subscription with no hierarchy relationship (default) | | `parent` | Owns the plan and line items; usage from child subscriptions rolls up here | | `inherited` | A skeleton subscription auto-created for each child customer; it carries no line items, and events are matched via the parent subscription | Customers remain flat entities. There are no parent fields on a customer record; hierarchy is configured only on subscriptions. **Key design principles (consolidated mode)** Hierarchy lives at the **subscription** layer, not on customer records. That gives you **clear billing ownership**, **centralized wallet management**, and **scalable multi-tenant usage tracking** on a single parent contract. The tradeoff: **child-level isolation is logical** (you can still break out analytics per child), **not financial**. Children with only inherited subscriptions are not separate billing entities for usage summary, wallet balance, or invoices. ## Visual flow (consolidated subscription) The diagrams and screenshots below apply to **consolidated subscription** mode: a `parent` subscription plus `inherited` skeleton subscriptions for each child. ### End-to-end flow ```mermaid theme={null} flowchart TB subgraph createParent [Parent_subscription_creation] A[Create_subscription_for_parent_customer] B[Configure_child_external_IDs_to_inherit] A --> B end subgraph linkChildren [Child_linkage] C[Inherited_skeleton_subscriptions_created] end subgraph usagePath [Usage_ingestion] D[Events_ingested_for_child_customers] E[Rolled_up_to_parent_contract] D --> E end subgraph invoices [Invoice_generation] F[Single_invoice_to_parent] G[One_line_item_per_feature_aggregated] F --> G end createParent --> linkChildren linkChildren --> usagePath usagePath --> invoices ``` ### Dashboard: add customers to inherit Use the **Add customers to inherit** flow when linking child customers to the parent subscription (wording and entry point may vary slightly by dashboard version). Add customers to inherit
modal ### Dashboard: inherited subscription on a child On a **child** customer, the **Inherited subscriptions** view shows the parent they inherit from, the plan, and key dates. Inherited subscriptions table on a child
customer The **parent subscription** creation screen (with inheritance configured) is shown in [Step 2: Create the parent subscription](#step-2-create-the-parent-subscription) below. ## Usage tracking behavior (inherited subscriptions) For **consolidated subscription** setups only (not delegated payer): * **Usage for billing** is tracked at the **parent** subscription. Inherited children do **not** keep a separate billing usage ledger. * [**Get customer usage summary**](/api-reference/customers/get-customer-usage-summary) (`getCustomerUsageSummary`, `GET /customers/usage`) for a **child** customer that only has an **inherited** subscription returns **no billable usage** on that child. Usage is attributed to the **parent** contract. * [**Get wallet balance**](/api-reference/wallets/get-wallet-balance) (`getWalletBalance`, `GET /wallets/{id}/balance/real-time`) does **not** represent an independent prepaid balance for inherited-only children. Query the **parent** customer's wallet for consolidated billing state. A child customer showing **zero** usage in usage-summary APIs while events are ingested for that customer is **expected** for inherited subscriptions. Confirm totals on the **parent** customer or use **analytics** (next section) for per-child breakdowns. ## Analytics behavior Analytics are more flexible than billing aggregates: * **Per-child contribution:** call [**Get usage analytics**](/api-reference/events/get-usage-analytics) (`getUsageAnalytics`, `POST /events/analytics`) with the **child** `external_customer_id` (and any filters you already use, such as time range or `group_by`). * **Aggregated view at the parent:** use the same endpoint scoped to the **parent** and include child usage in the aggregation: ```json theme={null} { "include_children": true } ``` Add this alongside the other fields your request already sends (for example `external_customer_id`, `start_time`, `end_time`, and `group_by`). ## Wallet and balance semantics (consolidated mode) In **consolidated subscription** mode: * The **wallet** used for settlement is the **parent** customer's wallet (the invoicing customer for the parent subscription). * **Balance** reflects credits and drawdowns **aggregated across all children** covered by that parent subscription. * Wallet-related APIs therefore reflect **global parent-level state**, not a separate financial balance per inherited child. ## Invoice behavior (consolidated mode) In **consolidated subscription** mode: * Invoices are generated **only** for the **parent** customer. * The invoice structure is **one line item per feature** (or priced line), with **usage from all children aggregated** into that line item. * There are **no child-level invoices** for inherited skeleton subscriptions. ## Consolidated subscription Use this when **one plan** covers multiple customers and you want **one invoice** to the parent (usage roll-up on a single contract). Flexprice implements this with a **parent** subscription plus **inherited** skeleton subscriptions for each child. Inherited subscriptions have no line items of their own; usage from child customers is matched and aggregated through the parent. **Example:** Global Corp HQ purchases an enterprise plan. APAC Team and EMEA Team each generate usage, but HQ receives one invoice. ### Step 1: Create customers Create the parent customer and each child customer through the Dashboard or API. Customer records have no hierarchy fields; they stay flat. ### Step 2: Create the parent subscription 1. Navigate to **Subscriptions** and click **Create Subscription** 2. Select the **parent customer** and choose a plan 3. In the **Customer Hierarchy** section, add the external IDs of the child customers who will inherit this subscription 4. Click **Create Subscription** Subscription create form showing Customer Hierarchy section with child customer IDs ```http theme={null} POST /subscriptions { "customer_id": "cus_hq_internal_id", "external_customer_id": "ext-global-hq", "plan_id": "plan_enterprise", "currency": "usd", "billing_period": "month", "billing_period_count": 1, "inheritance": { "external_customer_ids_to_inherit_subscription": [ "ext-apac-team", "ext-emea-team" ] } } ``` ### What Flexprice creates When you submit the request above, Flexprice: 1. Creates the **parent subscription** with `subscription_type: parent` for the HQ customer 2. Auto-creates an **inherited skeleton subscription** (`subscription_type: inherited`) for each child external ID 3. Keeps inherited subscriptions free of plan line items; usage events from child customers are matched and aggregated via the parent subscription ### Step 3: Verify Open the parent subscription's detail page. Child skeleton subscriptions appear under it. ## Delegated payer Use this when each end customer has their **own** subscription (plan, line items, and usage on the child), but a **different customer** should be the **bill-to** party: invoices, tax, and wallet drawdown follow that **delegated payer** (for example a parent company or reseller). **Example:** A reseller manages 10 end customers, each on their own starter plan. The reseller receives and pays all related invoices centrally. ### Step 1: Create customers Create the subscription owner (child) and the billing customer separately. Neither customer record has hierarchy fields. ### Step 2: Create the subscription with a delegated payer 1. Navigate to **Subscriptions** and click **Create Subscription** 2. Select the **child customer** as the subscription owner 3. Choose a plan 4. In the **Invoice To** field, select or enter the delegated payer's external ID (the customer who will receive the invoice) 5. Click **Create Subscription** ```http theme={null} POST /subscriptions { "customer_id": "cus_child_internal_id", "external_customer_id": "ext-end-customer", "plan_id": "plan_starter", "currency": "usd", "billing_period": "month", "billing_period_count": 1, "inheritance": { "invoicing_customer_external_id": "ext-reseller" } } ``` ### What Flexprice creates * Subscription is created with `subscription_type: standalone` on the child customer * Plan, line items, entitlements, and usage tracking all remain on the child * Invoices are issued to the delegated payer (`ext-reseller`) * The delegated payer's wallet and payment methods are used for settlement The invoicing customer is set at subscription creation and cannot be changed afterwards. If you need to change it, cancel the subscription and create a new one. ## Invoice and payment behavior | | Consolidated subscription | Delegated payer | | ------------------------- | --------------------------------------------------------------------------------------------- | --------------------------------------- | | **Usage for billing** | **Parent** subscription (rolled up; child usage-summary APIs show no isolated billable usage) | Child customer (their own subscription) | | **Invoice generated for** | Parent customer only (one line item per feature, usage aggregated across children) | Delegated payer (billing customer) | | **Wallet deducted from** | Parent customer | Delegated payer | | **Tax based on** | Parent customer's billing details | Delegated payer's billing details | | **Currency** | Set at the parent subscription | Set at the child subscription | ## Use cases **Enterprise with subsidiaries** *(Consolidated subscription)* Global HQ purchases a plan. Regional divisions (APAC, EMEA, Americas) each generate usage independently, but HQ receives **one consolidated invoice** to the parent, typically **one line item per feature** with usage **aggregated across divisions** (use analytics with `include_children` or per-child queries to see each division's contribution). **Reseller / partner model** *(Delegated payer)* A reseller signs up their clients as separate customers in Flexprice. Each client has their own subscription and usage tracking, but the reseller receives and pays all invoices. **Department-level billing** *(Consolidated subscription or delegated payer)* A company's central IT or Finance team handles vendor payments. Engineering, Marketing, and Sales each track their own usage, and invoices roll up to the central team (or the central team is set as the delegated payer, depending on your model). **Multi-brand holding company** *(Consolidated subscription)* A holding company owns multiple brands. Each brand operates independently as a customer with its own subscriptions. The holding company's finance department receives all invoices. ## Frequently asked questions In **consolidated subscription** mode, currency is set on the parent subscription and applies to all invoices generated for it. Child inherited subscriptions use the parent's currency. In **delegated payer** mode, each subscription sets its own currency independently, since each child owns a full subscription. Tax uses the **invoicing customer's** billing details: the parent customer in consolidated subscription mode, or the delegated payer in delegated payer mode. The subscription owner's billing address is not used for tax in either pattern. The wallet is deducted from the invoicing customer's balance, not the subscription owner's. In consolidated subscription mode, the parent customer's wallet is drawn down (balance reflects aggregated usage across inherited children). In delegated payer mode, the delegated payer's wallet is used. In **consolidated subscription** mode, billable usage is attributed to the **parent** subscription. A child with only an **inherited** subscription does not have a separate billing usage ledger, so usage-summary APIs for that child can correctly return **no usage**. Use the **parent** customer for billing summaries, or use [**Get usage analytics**](/api-reference/events/get-usage-analytics) for per-child breakdowns (and `include_children` when querying the parent for a rolled-up analytics view). Plan changes apply to the parent subscription only. Inherited skeleton subscriptions are not directly affected; they keep routing usage events to the updated parent. (This applies to **consolidated subscription** setups with a `parent` subscription.) A final invoice is generated for the invoicing customer. Inherited skeleton subscriptions linked to the parent are cancelled at the same time. (This applies to **consolidated subscription** mode.) No. The invoicing customer is set at creation time and is immutable. To change it, cancel the existing subscription and create a new one with the correct `invoicing_customer_external_id`. # Create subscription for a customer Source: https://docs.flexprice.io/docs/subscriptions/customers-create-subscription Once a customer is created, you can create a subscription for them by following these steps: * Navigate to the **Customers Dashboard**. * Click on the customer you want to create a subscription for. This will open the customer's details page. Select Customer * On the customer details page, click the **Add Subscription** button. * Select a pricing plan from the dropdown menu. Only plans that have been created will be listed. Select Plan * Based on the selected plan, subscription preview along with breakdown of charges will appear on the right side of the screen. This includes all recurring charges and usage-based charges that you've created in the plan for the given billing period. Subscription Preview * Specify the **Subscription Start Date** thats marks as the beginning of the billing cycle. * Define the **Subscription End Date** if required. By default, this is set to "Forever." * **Optional: Configure Override Line Items** - If you need to customize pricing for this specific subscription, you can add override line items to modify amounts, quantities, billing models, or tier structures. See [Subscription Override Line Items](/docs/subscriptions/override-line-items) for detailed information. * Once all details are entered, click **Add Subscription** to save. * The subscription will now appear under the customer's details with its status and billing information. Subscription Created ### **Managing Multiple Subscriptions for a Customer** * A single customer can have multiple active subscriptions. * Each subscription operates independently based on its assigned billing period, charges, and start date. * For example, a customer can have both a `Basic Plan` and an `Addon: AI Agent Sessions` subscription, each billed separately. ### **Customizing Subscription Pricing** * **Override Line Items**: Customize pricing for specific subscriptions by overriding plan prices * **Special Pricing**: Offer enterprise discounts, bulk pricing, or promotional rates * **Flexible Billing**: Modify billing models, tier structures, and quantities per subscription For comprehensive information on customizing subscription pricing, see [Subscription Override Line Items](/docs/subscriptions/override-line-items). # Subscription Override Line Items Source: https://docs.flexprice.io/docs/subscriptions/override-line-items Learn how to customize pricing for specific subscriptions by overriding plan prices with custom amounts, quantities, billing models, and tier structures. Subscription override line items allow you to customize pricing for specific subscriptions by overriding the default plan prices. This feature is useful when you need to offer special pricing, bulk discounts, or custom billing arrangements for individual customers. ## Overview Override line items enable you to modify various aspects of a subscription's pricing: * **Quantity**: Override the default quantity for a specific price * **Amount**: Set custom pricing amounts for specific line items * **Billing Model**: Change the billing model for a specific price * **Tier Mode**: Modify how tiered pricing is calculated * **Tiers**: Define custom pricing tiers for the subscription * **Transform Quantity**: Apply quantity transformations for package pricing ## When to Use Override Line Items Use override line items in these scenarios: * **Custom Pricing**: Offer special rates for enterprise customers * **Bulk Discounts**: Provide volume-based pricing adjustments * **Promotional Offers**: Apply temporary pricing changes * **Contract Negotiations**: Honor specific pricing agreements * **Regional Pricing**: Adjust prices based on customer location ## Structure Each override line item requires: ```json theme={null} { "price_id": "string (required)", "quantity": "decimal (optional)", "amount": "decimal (optional)", "billing_model": "string (optional)", "tier_mode": "string (optional)", "tiers": "array (optional)", "transform_quantity": "object (optional)" } ``` ### Required Fields * **`price_id`**: References the plan price to override (must exist in the selected plan) ### Optional Fields * **`quantity`**: Custom quantity for this line item * **`amount`**: New price amount that overrides the original price * **`billing_model`**: Override the billing model for this price * **`tier_mode`**: Change how tiered pricing is calculated * **`tiers`**: Define custom pricing tiers * **`transform_quantity`**: Apply quantity transformations ## Billing Model Overrides ### Flat Fee Billing Model When overriding to `FLAT_FEE`, you must provide either an `amount` or `quantity`: ```json theme={null} { "price_id": "price_123", "billing_model": "FLAT_FEE", "amount": "29.99" } ``` **Use Cases:** * Fixed monthly fees * One-time charges * Service-based pricing ### Tiered Billing Model When overriding to `TIERED`, you must provide either `tier_mode` or `tiers`: ```json theme={null} { "price_id": "price_123", "billing_model": "TIERED", "tier_mode": "VOLUME", "tiers": [ { "up_to": 1000, "unit_amount": "0.10", "flat_amount": "5.00" }, { "up_to": null, "unit_amount": "0.08" } ] } ``` **Tier Modes:** * **`VOLUME`**: All units priced based on the final tier reached * **`SLAB`**: Tiers apply progressively as quantity increases **Tier Structure:** * **`up_to`**: Quantity threshold (null for the last tier) * **`unit_amount`**: Price per unit in this tier * **`flat_amount`**: Additional flat fee for this tier (optional) ### Package Billing Model When overriding to `PACKAGE`, you must provide `transform_quantity`: ```json theme={null} { "price_id": "price_123", "billing_model": "PACKAGE", "transform_quantity": { "divide_by": 10, "round": "up" } } ``` **Transform Quantity Fields:** * **`divide_by`**: Number of units per package (must be > 0) * **`round`**: Rounding behavior (`"up"` or `"down"`) **Use Cases:** * Bulk pricing (e.g., 10 units for \$5.00) * Resource bundling * Simplified billing for large quantities ## Validation Rules ### General Requirements * At least one override field must be provided * All amounts must be non-negative * All quantities must be non-negative * Price ID must reference a valid price in the selected plan ### Billing Model Specific Rules #### Tiered Billing * Must provide either `tier_mode` or `tiers` * If `tiers` provided, each tier must have a valid `unit_amount` * `unit_amount` must be a valid decimal number * `flat_amount` (if provided) must be a valid decimal number #### Package Billing * `transform_quantity.divide_by` must be greater than 0 * `transform_quantity.round` must be either `"up"` or `"down"` * Defaults to `"up"` if not specified #### Flat Fee Billing * Must provide either `amount` or `quantity` ## Examples ### Basic Quantity Override ```json theme={null} { "price_id": "price_123", "quantity": "5.0" } ``` ### Custom Tiered Pricing ```json theme={null} { "price_id": "price_456", "billing_model": "TIERED", "tier_mode": "VOLUME", "tiers": [ { "up_to": 100, "unit_amount": "1.00" }, { "up_to": 500, "unit_amount": "0.75" }, { "up_to": null, "unit_amount": "0.50" } ] } ``` ### Package Pricing with Custom Rounding ```json theme={null} { "price_id": "price_789", "billing_model": "PACKAGE", "transform_quantity": { "divide_by": 25, "round": "down" } } ``` ### Multiple Overrides ```json theme={null} { "override_line_items": [ { "price_id": "price_123", "amount": "19.99" }, { "price_id": "price_456", "quantity": "10.0", "billing_model": "TIERED", "tier_mode": "SLAB", "tiers": [ { "up_to": 5, "unit_amount": "2.00" }, { "up_to": null, "unit_amount": "1.50" } ] } ] } ``` ## Best Practices ### 1. Plan Your Overrides * Identify which prices need customization * Understand the impact on billing calculations * Consider how overrides affect reporting ### 2. Validate Business Logic * Ensure override amounts make business sense * Verify tier structures are logical * Test edge cases with different quantities ### 3. Document Changes * Keep records of why overrides were applied * Note any special conditions or time limits * Update customer contracts if necessary ### 4. Monitor Usage * Track how overrides affect revenue * Identify patterns in override usage * Optimize pricing based on override data ## Common Use Cases ### Enterprise Discounts ```json theme={null} { "price_id": "enterprise_plan", "amount": "199.99", "quantity": "1.0" } ``` ### Volume Pricing ```json theme={null} { "price_id": "api_calls", "billing_model": "TIERED", "tier_mode": "VOLUME", "tiers": [ { "up_to": 10000, "unit_amount": "0.001" }, { "up_to": null, "unit_amount": "0.0005" } ] } ``` ### Promotional Offers ```json theme={null} { "price_id": "premium_feature", "amount": "0.00", "quantity": "1.0" } ``` ## Troubleshooting ### Common Issues 1. **Validation Errors** * Ensure at least one override field is provided * Check that amounts and quantities are non-negative * Verify price ID exists in the selected plan 2. **Billing Model Conflicts** * Tiered billing requires tier configuration * Package billing needs transform quantity * Flat fee requires amount or quantity 3. **Tier Configuration** * Validate tier boundaries are logical * Ensure last tier has `up_to: null` * Check unit amounts are valid decimals ### Error Messages * **"price not found in plan"**: Verify the price ID exists in the selected plan * **"at least one override field must be provided"**: Add at least one override field * **"invalid tier unit amount format"**: Ensure tier amounts are valid decimal numbers * **"transform\_quantity.divide\_by must be greater than 0"**: Set a positive package size ## Related Topics * [Creating Subscriptions](/docs/subscriptions/customers-create-subscription) * [Pricing Plans](/docs/product-catalogue/plans/pricing) * [Billing Models](/docs/product-catalogue/plans/billing-models/flat-fee) * [Viewing Subscriptions](/docs/subscriptions/view) *** *For technical implementation details, refer to the [API Reference](/api-reference/subscriptions/create-subscription).* # Plan Price Overrides Source: https://docs.flexprice.io/docs/subscriptions/plan-price-overrides Give a specific customer custom rates without changing the plan for everyone else. Apply overrides at subscription creation time. Plan price overrides let you customize pricing for a specific customer when creating a subscription. The plan itself stays unchanged — only this subscription uses the overridden rates. ## When to Use * Enterprise customer with a negotiated rate * Promotional or introductory pricing for a specific account * Volume discounts agreed outside of your standard plan tiers * Early adopter or partner pricing If you need to change pricing for **all** subscribers, update the plan price instead and run a price sync. See [Subscription Line Item Overrides](/docs/subscriptions/subscription-line-item-overrides). ## How to Apply Overrides Pass an `override_line_items` array in the create subscription request. Each entry references a plan price by `price_id` and specifies what to change. ```json theme={null} POST /subscriptions { "customer_id": "cust_xxx", "plan_id": "plan_xxx", "currency": "usd", "billing_cadence": "RECURRING", "billing_period": "MONTHLY", "override_line_items": [ { "price_id": "price_base_fee", "amount": "299.00" }, { "price_id": "price_api_calls", "billing_model": "TIERED", "tier_mode": "VOLUME", "tiers": [ { "up_to": 100000, "unit_amount": "0.0005" }, { "up_to": null, "unit_amount": "0.0002" } ] } ] } ``` You can override multiple prices in one request. ## What You Can Override | Field | Type | Notes | | -------------------- | ------- | -------------------------------------------------------- | | `price_id` | string | **Required.** The plan price to override | | `amount` | decimal | For flat fee prices (FIAT pricing) | | `quantity` | decimal | Fixed prices only — cannot be set for usage-based prices | | `billing_model` | string | `FLAT_FEE`, `TIERED`, or `PACKAGE` | | `tier_mode` | string | `VOLUME` or `SLAB` — for TIERED billing model | | `tiers` | array | Custom tier breakpoints for TIERED/FIAT pricing | | `transform_quantity` | object | For PACKAGE billing: `divide_by` and `round` | | `price_unit_amount` | decimal | For custom price unit prices | | `price_unit_tiers` | array | Custom tiers for custom price unit + TIERED | ## What You Cannot Override These fields are always inherited from the original plan price and cannot be changed: | Field | Why | | ----------------------------------------- | ------------------------------------- | | `currency` | Locked to the subscription's currency | | `billing_period` / `billing_period_count` | Billing cycle alignment is fixed | | `billing_cadence` | Cannot switch recurring ↔ one-time | | `invoice_cadence` | ARREAR/ADVANCE is fixed per price | | `trial_period` | Trial config is plan-level | | `meter_id` | The usage metric cannot be swapped | | `price_unit_type` | Cannot switch FIAT ↔ CUSTOM | | `display_name` | Preserved from original | ## Validation Rules * At least one override field must be provided per entry * `amount` and `quantity` must be non-negative * `quantity` is not allowed on usage-based prices (usage is metered, not quantity-based) * Cannot mix FIAT fields (`amount`, `tiers`) with custom price unit fields (`price_unit_amount`, `price_unit_tiers`) in the same entry * `transform_quantity.divide_by` must be greater than 0 * `price_id` must reference a price that exists in the selected plan ## How It Works Internally When an override is processed, Flexprice: 1. Creates a new **subscription-scoped price** with `entity_type = SUBSCRIPTION` and the overridden values 2. Sets `parent_price_id` on the new price to the original plan price's ID — preserving the lineage 3. The subscription's line item points to this subscription-scoped price The plan price is untouched. Other subscribers on the same plan are unaffected. ``` Plan Price (shared) └── Subscription-scoped Price (this customer only) └── Line Item → price_id = subscription-scoped price ``` ## Billing Behavior After Override * All invoicing for this subscription uses the subscription-scoped price * If the plan price is later updated, **this subscription's override is not affected** * If a [price sync](/docs/subscriptions/subscription-line-item-overrides) is run, **the overridden line item is not replaced** — sync only fills in missing line items ## Examples ### Flat Fee Discount ```json theme={null} { "price_id": "price_monthly_base", "amount": "199.00" } ``` ### Custom Usage Tiers ```json theme={null} { "price_id": "price_api_calls", "billing_model": "TIERED", "tier_mode": "VOLUME", "tiers": [ { "up_to": 50000, "unit_amount": "0.002" }, { "up_to": 200000, "unit_amount": "0.001" }, { "up_to": null, "unit_amount": "0.0005" } ] } ``` ### Package Pricing Override ```json theme={null} { "price_id": "price_sms", "billing_model": "PACKAGE", "transform_quantity": { "divide_by": 500, "round": "up" } } ``` ### Quantity Override (Fixed Price) ```json theme={null} { "price_id": "price_seats", "quantity": "50.0" } ``` ## Updating an Override After Creation To change an override on a live subscription, update the line item directly using the subscription line item update API. See [Subscription Line Item Overrides](/docs/subscriptions/subscription-line-item-overrides). ## Related Topics * [Creating Subscriptions](/docs/subscriptions/customers-create-subscription) * [Subscription Line Item Overrides](/docs/subscriptions/subscription-line-item-overrides) * [Plan Pricing](/docs/product-catalogue/plans/pricing) * [Billing Models](/docs/product-catalogue/plans/billing-models/flat-fee) # Progressive Billing Source: https://docs.flexprice.io/docs/subscriptions/progressive-billing Progressive billing automatically triggers mid-period invoices when a subscription's current-period usage crosses a defined threshold ## Overview Progressive billing lets you set a usage amount threshold on a subscription. When the customer's billed usage for the current period reaches that amount, FlexPrice automatically creates and finalizes a mid-period invoice without waiting for the period to end. This is useful for high-volume customers where waiting until period end creates cash flow or fraud risk. ## How It Works 1. You set `auto_invoice_threshold` on a subscription (e.g. \$500). 2. FlexPrice evaluates usage every **5 minutes** against the current window (`current_period_start` → now). 3. When usage ≥ threshold, a mid-period invoice is created and finalized immediately. 4. `current_period_start` advances to the moment the invoice was issued; `current_period_end` remains unchanged. Usage resets for the new sub-window and the threshold applies fresh. 5. At period end, a normal invoice is issued for remaining usage. Only **arrear usage charges** count toward the threshold. Fixed or advance charges are excluded. The invoice amount may slightly exceed the threshold since evaluation happens on a 5-minute interval, not in real time. ## Configuring Progressive Billing 1. Navigate to the **Subscriptions** section and open or create a subscription. 2. Locate the **Auto invoice threshold** section (progressive billing). 3. Enter your threshold amount. 4. Save the subscription. Progressive billing configuration: Auto invoice threshold Pass `auto_invoice_threshold` when creating a subscription: ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "cust_01xyz", "plan_id": "plan_01abc", "currency": "USD", "billing_cadence": "RECURRING", "billing_period": "MONTHLY", "billing_period_count": 1, "start_date": "2025-05-01T00:00:00Z", "auto_invoice_threshold": "500.00" }' ``` Response (abbreviated): ```json theme={null} { "id": "sub_01def456", "status": "active", "auto_invoice_threshold": "500.00", "current_period_start": "2025-05-01T00:00:00Z", "current_period_end": "2025-06-01T00:00:00Z" } ``` `auto_invoice_threshold` is set at the subscription level, not the plan level. Each subscription can have its own threshold. ## Invoicing Behavior | Scenario | Behavior | | ----------------------------------- | ------------------------------------------------------ | | Usage reaches threshold | Mid-period invoice created and finalized immediately | | Usage below threshold at period end | Normal period-end invoice covers all remaining usage | | Threshold not set (`null`) | Progressive billing disabled; standard billing applies | ## API Reference ### Subscription create fields | Field | Type | Required | Description | | ------------------------ | ---------------- | -------- | -------------------------------------------------------------------------------------------------- | | `auto_invoice_threshold` | string (decimal) | No | Usage amount that triggers a mid-period invoice. Must be > 0. `null` disables progressive billing. | ### Subscription response fields | Field | Type | Description | | ------------------------ | -------------------------- | ------------------------------------------------------------------ | | `auto_invoice_threshold` | string (decimal), nullable | The configured threshold for this subscription. `null` if not set. | ## Webhooks & Events No new webhook event type is introduced for progressive billing. Standard invoice events fire when a threshold invoice is created and finalized. Use the `billing_reason` field on the invoice object to identify threshold-triggered invoices: | Event | When it fires | | ------------------- | ---------------------------------------------------------------------- | | `invoice.created` | When usage crosses the threshold and a mid-period invoice is generated | | `invoice.finalized` | When the threshold invoice is finalized and ready for payment | The `billing_reason` on threshold invoices is `AUTO_INVOICE_THRESHOLD`. ## Best Practices **Set thresholds proportional to expected spend.** A threshold at 50 to 80% of expected monthly spend avoids too-frequent invoices while still providing early billing signals. **Ensure your payment method handles mid-period invoices.** Invoices created via progressive billing are finalized immediately. Make sure your payment collection flow can process charges outside the normal billing cycle. **Monitor via invoice webhooks.** No new webhook event type is introduced for progressive billing. Listen to standard invoice creation and finalization events; the invoice billing reason is `AUTO_INVOICE_THRESHOLD`, which you can use to distinguish these from regular period-end invoices. **Do not rely on real-time triggering.** There is up to a 5-minute delay between crossing the threshold and invoice creation. Do not use progressive billing for hard real-time spend caps. # Record payment for subscription Source: https://docs.flexprice.io/docs/subscriptions/record-payment Complete guide to Flexprice payment system including payment methods, behaviors, and wallet payment logic ## Overview Flexprice's payment system provides flexible subscription billing with intelligent payment processing. The system combines **payment behaviors** (how to handle payment failures) with **collection methods** (when to charge customers) to create a robust billing experience. ### Key Concepts * **Collection Method**: Determines when payments are collected (`charge_automatically` or `send_invoice`) * **Payment Behavior**: Defines how the system responds to payment failures * **Payment Methods**: Card payments and wallet/credits with intelligent splitting * **Flow Types**: Different contexts like subscription creation, renewals, manual payments * **Partial Payments**: Smart fallback to wallet payments when card payments fail ## Why Payment Behaviors and Collection Methods Matter Flexprice separates **when** to collect payment from **how** to handle failures. This design provides flexibility for different business models: ### Business Model Alignment * **SaaS Products**: Use `charge_automatically` + `allow_incomplete` for graceful failure handling * **Enterprise**: Use `send_invoice` + `default_active` for manual billing cycles * **Freemium**: Use `charge_automatically` + `default_active` for service-first approach * **Premium Services**: Use `charge_automatically` + `error_if_incomplete` for strict payment requirements ## Valid Payment Configurations The following table shows all valid combinations of collection methods, payment behaviors, and their outcomes: | Collection Method | Payment Behavior | Card Payment | Wallet Payment | Payment Success | Payment Failure | | ---------------------- | --------------------- | --------------- | --------------- | --------------- | ------------------- | | `charge_automatically` | `allow_incomplete` | ✅ Attempted | ✅ Fallback | ✅ Active | ⚠️ Incomplete | | `charge_automatically` | `error_if_incomplete` | ✅ Attempted | ❌ No fallback\* | ✅ Active | ❌ Failed/Incomplete | | `charge_automatically` | `default_active` | ✅ Attempted | ✅ Fallback | ✅ Active | ✅ Active | | `send_invoice` | `default_active` | ❌ Not attempted | ❌ Not attempted | ✅ Active | ✅ Active | | `send_invoice` | `default_incomplete` | ❌ Not attempted | ❌ Not attempted | ✅ Active | ⚠️ Incomplete | **Note**: \*Wallet fallback depends on flow type - see Partial Payment Logic section below. ### Invalid Combinations ❌ `charge_automatically` + `default_incomplete` - Conflicts with automatic charging\ ❌ `send_invoice` + `allow_incomplete` - No automatic payment to fail\ ❌ `send_invoice` + `error_if_incomplete` - No automatic payment to fail * When no collection method is provided, the default is `charge_automatically`. * When no payment behavior is provided, the default is `default_active`. ## Partial Payment Logic ### Overview When card payments fail, the system can attempt wallet payments as a fallback. This behavior depends on the **payment behavior** and **flow type** combination. ### Decision Matrix | Flow Type | Payment Behavior | Card Fails | Wallet Attempted | Rationale | | ----------------------- | --------------------- | ---------- | ---------------- | ----------------------------------- | | `subscription_creation` | `default_active` | ❌ | ✅ **Yes** | Subscription activates anyway | | `subscription_creation` | `allow_incomplete` | ❌ | ❌ **No** | Payment affects subscription status | | `subscription_creation` | `error_if_incomplete` | ❌ | ❌ **No** | Payment required for activation | | `renewal` | **Any** | ❌ | ✅ **Yes** | Partial payment better than none | | `manual` | **Any** | ❌ | ✅ **Yes** | Customer-initiated, allow partial | | `cancel` | **Any** | ❌ | ✅ **Yes** | Proration handling | ### Rules Summary * **Subscription Creation + Default Active**: Always allow partial wallet payment * **Other Subscription Creation**: No partial payment (affects activation) * **All Other Flows**: Always allow partial payment (better than failure) #### Wallet Categorization Wallets are categorized based on their `allowed_price_types` configuration: 1. **ALL Wallets**: Can pay any price type (USAGE + FIXED) 2. **USAGE Wallets**: Can only pay usage-based charges 3. **FIXED Wallets**: Can only pay fixed charges 4. **USAGE+FIXED Wallets**: Treated as ALL wallets #### Wallet Selection Algorithm ```mermaid theme={null} graph TD A[Get Customer Wallets] --> B[Filter Active Wallets] B --> C[Categorize by Price Type Restrictions] C --> D[USAGE Wallets] C --> E[FIXED Wallets] C --> F[ALL Wallets] D --> G[Sort by Balance Descending] E --> G F --> G G --> H[Return in Priority Order] ``` #### Payment Processing Order 1. **USAGE Wallets First**: Pay usage-based charges 2. **FIXED Wallets Second**: Pay fixed charges 3. **ALL Wallets Last**: Pay remaining amounts 4. **Balance Sorting**: Highest balance wallets used first ### **Endpoint**: `POST /v1/subscriptions` **Request**: ```json theme={null} { "customer_id": "cust_123", "plan_id": "plan_456", "collection_method": "charge_automatically", "payment_behavior": "allow_incomplete", "gateway_payment_method_id": "pm_789" } ``` **Parameters**: * `collection_method`: `"charge_automatically"` | `"send_invoice" ` (default: `"charge_automatically"`) * `payment_behavior`: `"allow_incomplete"` | `"error_if_incomplete"` | `"default_active"` | `"default_incomplete"` (default: `"default_active"`) * `gateway_payment_method_id` (optional): Override default payment method ## Payment Flow Architecture ```mermaid theme={null} graph TD A[Invoice Created] --> B{Collection Method?} B -->|charge_automatically| C[Payment Behavior Handler] B -->|send_invoice| D[Send Invoice to Customer] C --> E{Payment Behavior?} E -->|allow_incomplete| F[Attempt Payment + Continue on Failure] E -->|error_if_incomplete| G[Attempt Payment + Block on Failure] E -->|default_active| H[Activate + Attempt Payment] F --> I[Payment Processing] G --> I H --> I I --> J[Card Payment First] J --> K{Card Success?} K -->|Yes| L[Process Wallet Payment] K -->|No| M{Allow Partial Payment?} M -->|Yes| N[Attempt Wallet Payment] M -->|No| O[Payment Failed - Stop] L --> P[Payment Complete] N --> Q{Any Payment Made?} Q -->|Yes| R[Partial Payment Success] Q -->|No| O P --> S[Update Subscription Status] R --> S O --> S ``` ### Card-First Strategy The system prioritizes card payments with intelligent wallet fallback: 1. **Calculate Split**: Determine what wallets can pay based on price type restrictions 2. **Card First**: Attempt card payment for the full eligible amount 3. **Evaluate Result**: Check if card payment succeeded or failed 4. **Partial Decision**: Use behavior + flow type to decide on wallet fallback 5. **Wallet Payment**: Process available wallet payments if allowed 6. **Final Status**: Determine overall payment success and subscription status ## Payment Flow Types ### 1. Subscription Creation (`InvoiceFlowSubscriptionCreation`) **New subscription activation with payment collection** * **Behavior**: Applies full payment behavior logic * **Status Updates**: Subscription status depends on payment result and behavior * **Partial Payments**: Only allowed for `default_active` behavior ### 2. Renewal (`InvoiceFlowRenewal`) **Periodic billing for existing subscriptions** * **Behavior**: Non-blocking payment processing * **Status Updates**: Subscription continues regardless of payment result * **Partial Payments**: Always allowed (partial payment better than none) ### 3. Manual Payment (`InvoiceFlowManual`) **Customer-initiated payment attempts** * **Behavior**: Uses card-first logic with wallet fallback * **Status Updates**: Activates subscription only on full payment success * **Partial Payments**: Always allowed ### 4. Cancellation (`InvoiceFlowCancel`) **Proration handling during subscription cancellation** * **Behavior**: Processes final charges/credits * **Status Updates**: No subscription status changes * **Partial Payments**: Always allowed ## Payment Methods ### 1. Card Payments (`CARD`) **Primary payment method for automatic billing** * **Processing**: Immediate via Stripe integration * **Requirements**: Customer must exist in Stripe with valid payment method * **Gateway Support**: Can specify `gateway_payment_method_id` for subscription-specific cards * **Fallback**: Wallet payment attempted if card fails (based on partial payment rules) ### 2. Credits/Wallet Payments (`CREDITS`) **Prepaid balance payments with price type restrictions** * **Processing**: Instant, no external dependencies * **Restrictions**: Wallets can be limited to USAGE, FIXED, or ALL price types * **Multi-Wallet**: Supports multiple wallets per customer with intelligent selection * **Priority**: Promotional wallets used before paid wallets ### 3. Offline Payments (`OFFLINE`) **Manually recorded payments for enterprise customers** * **Processing**: Manual reconciliation required * **Use Cases**: Wire transfers, checks, cash payments * **Status**: Immediate success upon manual confirmation ### 4. Payment Link (`PAYMENT_LINK`) **Customer-initiated payments through hosted checkout** * **Processing**: Redirects to external payment page * **Features**: Supports 3D Secure, multiple payment methods * **Integration**: Webhook-based status updates ## API Reference ### Create Subscription with Payment Configuration **Endpoint**: `POST /v1/subscriptions` **Request**: ```json theme={null} { "customer_id": "cust_123", "plan_id": "plan_456", "collection_method": "send_invoice", "payment_behavior": "default_active", "gateway_payment_method_id": "pm_789" } ``` **Parameters**: * `collection_method`: `"charge_automatically"` | `"send_invoice"` * `payment_behavior`: `"allow_incomplete"` | `"error_if_incomplete"` | `"default_active"` | `"default_incomplete"` * `gateway_payment_method_id` (optional): Override default payment method ## Payment Method Strategy * **Primary**: Configure card payments for automatic billing * **Fallback**: Set up wallet payments for partial payment scenarios * **Enterprise**: Enable offline payments for wire transfers * **Customer Choice**: Offer payment links for flexible payment options ## Troubleshooting ### Common Issues 1. **Payment Declined**: Verify customer's payment method and billing details 2. **Wallet Restrictions**: Check wallet `allowed_price_types` match invoice charges 3. **Subscription Not Activating**: Verify payment behavior allows activation on failure 4. **Partial Payments**: Check flow type and behavior combination in decision matrix For complete API documentation, see: * [Create Subscription API](/api-reference/subscriptions/create-subscription) * [Attempt Payment API](/api-reference/invoices/attempt-invoice-payment) # Reservation / Commitment Discount Source: https://docs.flexprice.io/docs/subscriptions/reservation-discount Reservation discount (also called **commitment discount**) lets you guarantee a minimum spend or usage level on any metered charge, charge a premium for usage above that level, and optionally enforce the minimum even when actual usage falls short all configurable per charge, per subscription. ### Who is it for? * **Cloud infrastructure** — GPU/CPU reservations, storage commitments * **AI / ML platforms** — inference-hour commitments, training-job minimums * **SaaS & API providers** — API-call or data-transfer commitments *** ## Key Terminology | | | | ----------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Commitment** | The guaranteed minimum a customer agrees to for a billing period. Can be expressed as a currency amount (e.g. \$1,000/month) or a unit quantity (e.g. 500 GPU-hours/month). | | **Overage** | Usage above the commitment. Charged at **unit price × overage factor** — the factor can add a premium (1.5×), keep the same rate (1.0×), or even discount excess (0.8×). | | **Overage Factor** | A multiplier applied to the excess cost beyond the commitment. 1.5× = 50% premium, 1.0× = no premium, 0.8× = 20% discount on excess. | | **True-Up** | When enabled, the customer always pays at least the full commitment amount — even if actual usage is lower. The shortfall is added as a true-up fee. When disabled, the customer pays only for what they used. | | **Commitment Type: Amount** | Customer commits to a minimum **amount** per period. | | **Commitment Type: Quantity** | Customer commits to a minimum **number of units** per period. The commitment cost is derived as quantity × unit price. | *** ## How Billing Works For each billing period, Flexprice evaluates one of two outcomes: ```mermaid theme={null} flowchart TD A[Usage Reported] --> B{Usage ≥ Commitment?} B -- Yes --> C[Charge: Commitment + Overage] B -- No --> D{True-Up Enabled?} D -- Yes --> E[Charge: Full Commitment Amount] D -- No --> F[Charge: Actual Usage Only] ``` * **Usage ≥ Commitment →** Total = Commitment + (Usage Cost − Commitment) × Overage Factor. The customer pays the committed amount at standard rate, plus a premium on everything above it. * **Usage \< Commitment, True-Up enabled →** Customer pays the full commitment amount (shortfall added as true-up fee). * **Usage \< Commitment, True-Up disabled →** Customer pays only for actual usage. *** ## Example 1 — AI Inference Platform (Per-Period Commitment) An AI platform sells inference compute at **\$2 / vCPU-hour**. They want customers to commit to a minimum of **500 vCPU-hours/month** and charge a **1.5× premium** on any usage above that. | Parameter | Value | | -------------- | -------------------------------------- | | Metered charge | vCPU-hours | | Unit price | \$2 / vCPU-hour | | Commitment | 500 vCPU-hours/month (= \$1,000/month) | | Overage factor | 1.5× | | True-up | Enabled | The vCPU-hours line item is configured with a unit price of \$2 / vCPU-hour in the pricing plan. Pricing configured in the plan Navigate to **Customers** → Select a customer → **Add Subscription**, and select your pricing plan. Once the plan is loaded, click the three-dot menu (`...`) next to the vCPU-hours charge and select **Configure Commitment**. Select 'Configure Commitment' from the line item menu In the configuration modal, set the commitment type to **Quantity** with a value of **500**, apply an overage factor of **1.5x**, and enable **True Up**. Save the commitment and proceed to create the subscription. Configure commitment type, quantity, overage factor, and true-up ``` Overage qty: 700 − 500 = 200 vCPU-hours Commitment: 500 vCPU-hours × $2 = $1,000 Overage: 200 vCPU-hours × ($2 × 1.5) = $600 Total Invoice: $1,000 + $600 = $1,600 ``` True-up is enabled so the full commitment is enforced. ``` Shortfall qty: 500 − 300 = 200 vCPU-hours Usage cost: 300 vCPU-hours × $2 = $600 True-up fee: 200 vCPU-hours × $2 = $400 Total Invoice: $600 + $400 = $1,000 ``` Same usage but with true-up turned off. The customer pays only for what they consumed. ``` Usage cost: 300 vCPU-hours × $2 = $600 Total Invoice: = $600 ``` *** ## Example 2 — Cloud Infra Company (Windowed Commitment) A cloud infrastructure company sells GPU compute at **\$2 / GPU-hour**. Instead of a single monthly commitment, they want to commit customers on an **hourly basis** — a minimum of **10 GPU-hours every hour**. Each hour settles independently and the totals are summed at the end of the billing period. Windowed commitment is available only on **bucketed** features. The window size is inherited from the meter's bucket configuration. | Parameter | Value | | -------------- | ----------------------------------- | | Metered charge | GPU-hours | | Unit price | \$2 / GPU-hour | | Commitment | 10 GPU-hours per hour (= \$20/hour) | | Overage factor | 1.5× | | True-up | Enabled | | Window | Hourly | The GPU-hours line item is configured with a unit price of \$2 / GPU-hour in the pricing plan. Pricing configured in the plan Navigate to **Customers** → Select a customer → **Add Subscription**, and select your pricing plan. Once the plan is loaded, click the three-dot menu (`...`) next to the GPU-hours charge and select **Configure Commitment**. Select 'Configure Commitment' from the line item menu In the configuration modal, set the commitment type to **Quantity** with a value of **10**. Apply a **1.5x** overage factor, enable both **True Up** and **Windowed Commitment**, and save your changes before creating the subscription. Configure commitment for windowed setup ``` Overage qty: 15 − 10 = 5 GPU-hours Commitment: 10 GPU-hours × $2 = $20 Overage: 5 GPU-hours × ($2 × 1.5) = $15 Hour 1 charge: $20 + $15 = $35 ``` ``` Shortfall qty: 10 − 6 = 4 GPU-hours Usage cost: 6 GPU-hours × $2 = $12 True-up fee: 4 GPU-hours × $2 = $8 Hour 2 charge: $12 + $8 = $20 ``` ``` Usage cost: 10 GPU-hours × $2 = $20 Hour 3 charge: = $20 ``` ``` Total: $35 + $20 + $20 = $75 ``` *** Reservation discount applies per-charge commitment and overage. For a single minimum spend across the **entire subscription**, see [Commitment](/docs/subscriptions/commitment/overview). # Seat-Based Pricing Source: https://docs.flexprice.io/docs/subscriptions/seat-based-pricing Bill customers per user, seat, or license. Set quantity at subscription creation and adjust it at any time with optional future-dating. Seat-based pricing lets you charge customers based on the number of seats (users, licenses, or any countable unit) they provision. Set the initial seat count when creating a subscription, then increase or decrease it at any time - immediately or on a future date - using the modification API. ## When to Use * SaaS products that charge per user (e.g. \$20/user/month) * License-based billing (editor seats, API keys, agent instances) * Hybrid plans: a fixed base fee plus per-seat charges * Enterprise contracts with minimum seat commitments If charges are driven by events (API calls, compute time, etc.), use [Event Ingestion](/docs/event-ingestion/overview) instead. Seat-based billing is for known, provisioned quantities, not metered consumption. ## How It Works Seat-based billing has three stages: 1. **Configure a per-seat price** on a plan - a fixed price with `billing_model: FLAT_FEE` and a per-unit `amount`. 2. **Create a subscription** and set the initial seat count via `line_items[].quantity` (or `override_line_items[].quantity` for a customer-specific count). 3. **Adjust seat count** mid-subscription using the modification API with an optional `effective_date`. The billing engine multiplies `amount x quantity` when generating invoice line items each billing period. ## Step 1: Configure a Per-Seat Price on Your Plan A per-seat price is a fixed price with `FLAT_FEE` billing model. The `amount` is the cost per seat per billing period. ```json theme={null} POST /prices { "entity_type": "plan", "entity_id": "plan_01JVKM3P9YQRS5GHTU0MCXA4T", "type": "FIXED", "currency": "usd", "amount": "20.00", "billing_model": "FLAT_FEE", "billing_period": "MONTHLY", "billing_period_count": 1, "invoice_cadence": "ADVANCE", "description": "Per seat, billed monthly", "lookup_key": "seat_monthly" } ``` | Field | Value | Notes | | ---------------------- | ----------------------------- | ------------------------------------------------------------------------ | | `entity_type` | `"plan"` | Attaches this price to a plan | | `entity_id` | plan ID | The plan this price belongs to | | `type` | `"FIXED"` | Fixed charge (not metered). Use `"USAGE"` for metered prices | | `amount` | `"20.00"` | Cost per seat per billing period | | `billing_model` | `"FLAT_FEE"` | Multiplies amount x quantity | | `billing_period` | `"MONTHLY"` / `"ANNUAL"` etc. | How often the seat charge recurs | | `billing_period_count` | `1` | Number of periods per cycle. `1` with `MONTHLY` means charge every month | | `invoice_cadence` | `"ADVANCE"` | Bill at the start of each period (typical for seats) | **Package pricing for seat buckets:** If you charge per block of seats (e.g. \$100 per 5 seats), use `billing_model: PACKAGE` and set `transform_quantity.divide_by: 5`. The billing engine divides the raw quantity by 5 before applying the price. ## Step 2: Create a Subscription with an Initial Seat Count Set the seat count in `line_items` when creating the subscription. Reference the price by its `price_id` and pass the initial quantity. ```json theme={null} POST /subscriptions { "customer_id": "cust_01JVKM2N8XPQR4FGHT9LBWZ3S", "plan_id": "plan_01JVKM3P9YQRS5GHTU0MCXA4T", "currency": "usd", "billing_cadence": "RECURRING", "billing_period": "MONTHLY", "billing_cycle": "anniversary", "line_items": [ { "price_id": "price_01JVKM4Q0ZRST6HITUV1NDYB5", "quantity": "25" } ] } ``` The response includes the created subscription with `line_items[].id`. Save this ID - you will need it in Step 3 to modify seat count. ```json theme={null} { "id": "sub_01JVKM6S2BTUV8JKVWX3PFAD7", "status": "ACTIVE", "customer_id": "cust_01JVKM2N8XPQR4FGHT9LBWZ3S", "plan_id": "plan_01JVKM3P9YQRS5GHTU0MCXA4T", "line_items": [ { "id": "li_01JVKM5R1ASTU7IJUVW2OEZC6", "price_id": "price_01JVKM4Q0ZRST6HITUV1NDYB5", "quantity": "25", "price_type": "FIXED", "start_date": "2026-06-01T00:00:00Z", "end_date": null } ] } ``` This creates a subscription where 25 seats x $20 = **$500/month\*\* is charged at the start of each period. ### Customer-Specific Seat Count (Override) For enterprise customers with a negotiated seat count, use `override_line_items` instead. The plan stays unchanged - only this subscription uses the overridden quantity. ```json theme={null} POST /subscriptions { "customer_id": "cust_01JVKN3P0ZRST7IJUVW3OEZD7", "plan_id": "plan_01JVKM3P9YQRS5GHTU0MCXA4T", "currency": "usd", "billing_cadence": "RECURRING", "billing_period": "ANNUAL", "billing_cycle": "anniversary", "override_line_items": [ { "price_id": "price_01JVKM4Q0ZRST6HITUV1NDYB5", "quantity": "500" } ] } ``` `quantity` cannot be set on usage-based (metered) prices. It is only valid on fixed prices with `FLAT_FEE`, `PACKAGE`, or `TIERED` billing models. Creating a subscription with seat count ## Step 3: Adjust Seat Count on a Live Subscription Use the subscription modification API to change the seat count after the subscription is active. You can apply the change immediately or schedule it for a future date. ### 3a. Find the Line Item ID The modification API takes a line item ID (`li_...`), not a price ID. The `line_items[].id` is returned when you create the subscription (see Step 2) or when you fetch it: ``` GET /subscriptions/sub_01JVKM6S2BTUV8JKVWX3PFAD7 ``` Match the line item to the right price via `line_items[].price_id`. ### 3b. Preview the Change (Recommended) Before executing, preview the billing impact: ```json theme={null} POST /subscriptions/sub_01JVKM6S2BTUV8JKVWX3PFAD7/modify/preview { "type": "quantity_change", "quantity_change_params": { "line_items": [ { "id": "li_01JVKM5R1ASTU7IJUVW2OEZC6", "quantity": "40" } ] } } ``` Review changes preview The UI preview shows the old and new line item periods and confirms a proration invoice will be generated. The API returns the same information in `changed_resources`: ```json theme={null} { "subscription": { "id": "sub_01JVKM6S2BTUV8JKVWX3PFAD7", "..." : "..." }, "changed_resources": { "line_items": [ { "id": "li_01JVKM5R1ASTU7IJUVW2OEZC6", "price_id": "price_01JVKM4Q0ZRST6HITUV1NDYB5", "quantity": "25", "start_date": "2026-06-01T00:00:00Z", "end_date": "2026-06-10T14:32:00Z", "change_action": "ended" }, { "id": "li_01JVKN7T3CUVW9KLVWX4QGBE8", "price_id": "price_01JVKM4Q0ZRST6HITUV1NDYB5", "quantity": "40", "start_date": "2026-06-10T14:32:00Z", "end_date": null, "change_action": "created" } ], "invoices": [ { "id": "inv_01JVKN8U4DVWX0LMWXY5RHCF9", "action": "created", "status": "preview", "invoice": { "amount_due": "203.23", "currency": "usd" } } ] } } ``` Nothing is billed until you call the execute endpoint. The `status: "preview"` on the invoice confirms this is a dry run. ### 3c. Execute an Immediate Seat Change Omit `effective_date` to apply the change right now: ```json theme={null} POST /subscriptions/sub_01JVKM6S2BTUV8JKVWX3PFAD7/modify/execute { "type": "quantity_change", "quantity_change_params": { "line_items": [ { "id": "li_01JVKM5R1ASTU7IJUVW2OEZC6", "quantity": "40" } ] } } ``` The current line item is terminated immediately and a new one starts with `quantity: 40`. The response has the same shape as the preview but with `status: "issued"` on the invoice, confirming the charge was generated: ```json theme={null} { "subscription": { "id": "sub_01JVKM6S2BTUV8JKVWX3PFAD7", "..." : "..." }, "changed_resources": { "line_items": [ { "id": "li_01JVKM5R1ASTU7IJUVW2OEZC6", "price_id": "price_01JVKM4Q0ZRST6HITUV1NDYB5", "quantity": "25", "start_date": "2026-06-01T00:00:00Z", "end_date": "2026-06-10T14:32:00Z", "change_action": "ended" }, { "id": "li_01JVKN7T3CUVW9KLVWX4QGBE8", "price_id": "price_01JVKM4Q0ZRST6HITUV1NDYB5", "quantity": "40", "start_date": "2026-06-10T14:32:00Z", "end_date": null, "change_action": "created" } ], "invoices": [ { "id": "inv_01JVKN8U4DVWX0LMWXY5RHCF9", "action": "created", "status": "issued", "invoice": { "amount_due": "203.23", "currency": "usd" } } ] } } ``` ### 3d. Schedule a Future-Dated Seat Change Pass an `effective_date` to defer the change: ```json theme={null} POST /subscriptions/sub_01JVKM6S2BTUV8JKVWX3PFAD7/modify/execute { "type": "quantity_change", "quantity_change_params": { "line_items": [ { "id": "li_01JVKM5R1ASTU7IJUVW2OEZC6", "quantity": "40", "effective_date": "2026-07-01T00:00:00Z" } ] } } ``` What happens internally: 1. The current line item's `end_date` is set to `2026-07-01T00:00:00Z` 2. A new line item is created with `start_date: 2026-07-01T00:00:00Z` and `quantity: 40` 3. Billing continues at the old quantity until July 1, then switches to 40 seats Use this when a customer confirms they'll add seats at the start of next month and you don't want a mid-period invoice now. Adjusting seat count in the UI ## Proration on Seat Changes When you change seat count mid-period, Flexprice prorates based on days remaining in the current billing period. ### Upgrade (Adding Seats): Mid-Month Example **Before change:** 25 seats x $20 = $500/month, billed on the 1st. **Change on day 10:** Increase to 40 seats. **Days remaining:** 21 of 31. | Line item | Calculation | Amount | | ------------------------------------- | --------------- | ------------ | | Credit for unused days at 25 seats | (21/31) x \$500 | -\$338.71 | | Charge for remaining days at 40 seats | (21/31) x \$800 | +\$541.94 | | **Net invoice** | | **\$203.23** | ### Downgrade (Removing Seats): Mid-Month Example **Before change:** 40 seats x $20 = $800/month, billed on the 1st. **Change on day 10:** Decrease to 25 seats. **Days remaining:** 21 of 31. | Line item | Calculation | Amount | | ------------------------------------- | --------------- | ------------- | | Credit for unused days at 40 seats | (21/31) x \$800 | -\$541.94 | | Charge for remaining days at 25 seats | (21/31) x \$500 | +\$338.71 | | **Net** | | **-\$203.23** | What gets issued depends on the price's `invoice_cadence`: * **`ADVANCE`** (billed at period start): the customer was already charged for the full period, so a **refund** of \$203.23 is issued. * **`ARREAR`** (billed at period end): nothing has been charged yet, so a **one-off invoice** for $338.71 (the remaining days at 25 seats) is generated immediately and the $541.94 credit offsets the end-of-period invoice. ### Skip Proration: Change at Period End To avoid a mid-period invoice entirely, set `effective_date` to the start of the next billing period. The old seat count bills through the end of the current period and the new count takes effect at renewal. ## Seat-Based Billing in the UI You can manage seat counts from the Subscription detail page in the Flexprice dashboard without touching the API. **Viewing seat history:** The subscription timeline shows every quantity change with its effective date and the invoice it triggered. Seat change history on subscription **Viewing the prorated invoice:** When a mid-period seat change generates an invoice, the invoice detail shows two line items: the credit for the old quantity and the charge for the new quantity. Invoice line items for a seat change ## Common Patterns | Situation | Approach | | ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | | Set initial seat count at subscription creation | `line_items[].quantity` in `POST /subscriptions` | | Override seat count for one enterprise customer | `override_line_items[].quantity` in `POST /subscriptions` | | Increase seats immediately with proration | `POST /subscriptions/{id}/modify/execute` without `effective_date` | | Decrease seats at the end of the current period | `POST /subscriptions/{id}/modify/execute` with `effective_date` = start of next period | | Preview billing impact before changing seats | `POST /subscriptions/{id}/modify/preview` | | Change seats and also change the plan tier | Use [Upgrade & Downgrade](/docs/subscriptions/upgrade-and-downgrade) | | Give one customer a different per-seat rate | Use [Plan Price Overrides](/docs/subscriptions/plan-price-overrides) with an `amount` override | | Track seat usage via events (identity-linked) | Use a metered feature with `COUNT_UNIQUE` aggregation - see [Features](/docs/product-catalogue/features/overview) | ## Edge Cases & Gotchas **`quantity` lives on the line item, not the subscription.** There is no top-level `quantity` field on a subscription object. It lives at `subscription.line_items[].quantity`. If your plan has multiple prices (e.g. a base fee plus a per-seat charge), only the seat price's line item needs a custom quantity. Leave the base fee line item at `quantity: 1`. **You need the line item ID, not the price ID.** The modification API takes `line_items[].id` (format: `li_01...`), not `price_id`. Fetch the subscription first with `GET /subscriptions/{id}` to find it. **Usage-based prices reject quantity.** Setting `quantity` on a metered price line item returns an error. Usage is computed from ingested events, not a quantity field. **Future-dated changes produce two line item records.** Between the request and `effective_date`, two records exist for the same price: the old one ending at `effective_date` and the new one starting at it. Only one is active at any point in time. Do not delete or modify the old record before the effective date fires. **Multiple seat changes in one period are fine.** Each change prorates against the state at the time of the call. The billing engine generates a separate invoice per change. **Setting quantity to zero stops billing for that line item.** The remaining period's charge is credited in full. Use with care. ## Related Topics * [Creating Subscriptions](/docs/subscriptions/customers-create-subscription) * [Plan Price Overrides](/docs/subscriptions/plan-price-overrides) * [Subscription Line Item Overrides](/docs/subscriptions/subscription-line-item-overrides) * [Understanding Proration](/docs/subscriptions/understanding-proration) * [Upgrade & Downgrade](/docs/subscriptions/upgrade-and-downgrade) * [Plan Pricing Models](/docs/product-catalogue/plans/pricing) # Subscription Line Item Overrides Source: https://docs.flexprice.io/docs/subscriptions/subscription-line-item-overrides Add, update, or remove individual charges on a live subscription, and push plan pricing changes to existing subscribers using the price sync workflow. A subscription's line items are the actual billing records — one per price. After a subscription is live, you can add new line items, update existing ones, remove them, or push plan-level pricing changes to all subscribers using the price sync workflow. ## What Is a Line Item? Each line item on a subscription: * References one price (`price_id`) * Has its own `start_date` and `end_date` * Is what the billing engine uses to generate invoice charges * Can point to a plan price (standard) or a subscription-scoped price (overridden) ## Adding a Line Item Use this when you want to add a charge that wasn't part of the original subscription — for example, an add-on price or a usage meter you've just enabled. ``` POST /subscriptions/{id}/line-items ``` ```json theme={null} { "price_id": "price_new_feature", "quantity": "1.0", "start_date": "2026-04-01T00:00:00Z" } ``` If the price is usage-based, `quantity` is automatically set to `0` — usage is computed from metered events. ## Updating a Line Item Use this to change the pricing or quantity of an existing line item. ``` PATCH /subscriptions/{id}/line-items/{line_item_id} ``` When you change pricing fields (amount, tiers, billing model), the system: 1. Terminates the old line item at `effective_from` 2. Creates a new subscription-scoped price with the updated values 3. Creates a new line item starting at `effective_from` When you only change metadata or commitment fields, the line item is updated in-place — no new price is created. ## Removing a Line Item Use this to stop billing for a specific charge. ``` DELETE /subscriptions/{id}/line-items/{line_item_id} ``` ```json theme={null} { "effective_from": "2026-04-01T00:00:00Z" } ``` This sets `end_date` on the line item. The line item record is retained for billing history. Billing stops at the effective date. ## Pushing Plan Price Changes to Existing Subscriptions (Price Sync) When you update a plan's prices, existing subscriptions are **not automatically updated**. The price sync workflow propagates plan changes to all active subscribers on a plan. ### What Sync Does **Phase 1 — Terminate expired line items:** Finds line items for plan prices that have an `end_date` in the past. Sets `line_item.end_date = price.end_date`. Those charges stop billing. **Phase 2 — Create missing line items:** Finds plan prices that do not have a corresponding line item on each active subscription. Creates the missing line items with values from the plan price. | Field set by sync | Value | | ---------------------- | ------------------------------------ | | `price_id` | Plan price ID | | `quantity` | `0` for usage prices; `1` for fixed | | `start_date` | `price.start_date` (from plan price) | | `end_date` | `price.end_date` (from plan price) | | `metadata["added_by"]` | `"plan_sync_api"` | **What sync does NOT do:** * Does not update line items that already exist * Does not overwrite line items with overridden (subscription-scoped) prices * Does not delete line items for plan prices that were removed (set `price.end_date` to stop billing for removed prices) ### Recommended Sequence for Plan Pricing Changes **Step 1 — Update plan prices** ``` POST /plans/{id}/prices ← add new price PUT /plans/{id}/prices/{id} ← update existing price ``` **Step 2 — Check for a running sync** ```json theme={null} POST /workflows/search { "workflow_type": "PriceSyncWorkflow", "entity_id": "", "workflow_status": "Running" } ``` If `pagination.total > 0`, a sync is already running. Wait for it before triggering a new one. **Step 3 — Trigger sync** ``` POST /plans/{id}/sync/subscriptions ``` Response: ```json theme={null} { "workflow_id": "PriceSyncWorkflow-plan_xxx", "run_id": "run_abc123", "message": "price sync workflow started successfully" } ``` **Step 4 — Poll until complete** ``` GET /workflows/{workflow_id}/{run_id} ``` Poll every 30 seconds. Check `status` in the response. **Step 5 — Handle failure** On `Failed`, call `GET /workflows/{workflow_id}/{run_id}` to review the error context and re-trigger after fixing the root cause. ### Workflow Status Values | Status | Meaning | | ------------ | ----------------------------------- | | `Running` | Currently executing | | `Completed` | Finished successfully | | `Failed` | Terminated with an error | | `Canceled` | Manually canceled | | `Terminated` | Force-terminated | | `TimedOut` | Exceeded the 1-hour timeout | | `Unknown` | Status not yet synced from Temporal | ## Date Handling ### Line Item Start Date ``` line_item.start_date = max( subscription.start_date, price.start_date (if set), request.start_date (if set) ) ``` The latest of the three applies. Timestamps are stored in UTC, truncated to milliseconds. ### Line Item End Date ``` line_item.end_date = request.end_date (if provided) = subscription.end_date (if subscription has one) = nil (open-ended) ``` ### When Sync Creates Line Items Sync uses the plan price's dates directly: `start_date = price.start_date`, `end_date = price.end_date`. If the plan price has no dates, the created line item also has no dates (open-ended). ### All Combinations | Scenario | `line_item.start_date` | `line_item.end_date` | | -------------------------------------- | -------------------------- | ------------------------------ | | Price has no start/end dates | `subscription.start_date` | `subscription.end_date` or nil | | `price.start_date` \< `sub.start_date` | `sub.start_date` | `sub.end_date` or nil | | `price.start_date` > `sub.start_date` | `price.start_date` | `sub.end_date` or nil | | `price.end_date` set | per above | `price.end_date` (via sync) | | Request includes `start_date` | `max(sub, price, request)` | per above | ### Validation * `line_item.start_date >= subscription.start_date` * `line_item.end_date <= subscription.end_date` (when subscription has one) * `line_item.start_date <= line_item.end_date` (when both set) ## Decision Guide | Situation | Right Approach | | ----------------------------------------------------------------- | --------------------------------------------------------------- | | Give one customer a different rate at subscription creation | [Plan price override](/docs/subscriptions/plan-price-overrides) | | Change pricing for all subscribers on a plan | Update plan price → run price sync | | Add a new charge to one live subscription | `POST /subscriptions/{id}/line-items` | | Add a new price to a plan and push to all subscribers | Add price to plan → run price sync | | Remove a charge from one subscription | `DELETE /subscriptions/{id}/line-items/{id}` | | Remove a price from a plan and stop billing everyone | Set `price.end_date` → run price sync | | A subscriber has an override — push global pricing update to them | Update their line item directly (sync won't touch overrides) | | Change the amount on one subscription's charge | Update line item with new amount | ## Edge Cases & Gotchas **Overrides survive sync.** Sync only creates items for missing `(subscription_id, price_id)` pairs. An overridden line item already exists (pointing to a subscription-scoped price), so sync never replaces it. **Removing a plan price does not stop billing automatically.** Sync has no delete phase. To stop billing for a plan price, set `price.end_date`. The sync termination phase will then close the line items. **Prices without dates don't expire.** If a plan price has no `start_date` or `end_date`, line items created by sync have no date bounds and remain active until the subscription ends or you manually terminate them. **Concurrent sync protection.** The sync API uses a Redis distributed lock (2-hour TTL). Always check for a running workflow before triggering to avoid redundant runs. **Usage prices always get quantity = 0.** Setting `quantity` on a usage-based price override is rejected. Usage is computed from ingested events, not the line item quantity. ## Related Topics * [Plan Price Overrides](/docs/subscriptions/plan-price-overrides) * [Creating Subscriptions](/docs/subscriptions/customers-create-subscription) * [Plan Pricing](/docs/product-catalogue/plans/pricing) * [Event Ingestion](/docs/event-ingestion/overview) * [Pause & Resume Workflows](/docs/subscriptions/workflows/pause) # Understanding Proration Source: https://docs.flexprice.io/docs/subscriptions/understanding-proration Learn how proration works when changing subscription plans and how it affects your billing ## What is Proration? Proration is the process of calculating proportional charges and credits when you change your subscription plan mid-billing period. It ensures you only pay for what you use and get credited for unused time. ## How Proration Works ### Basic Concept When you change plans, Flexprice: 1. **Calculates unused time** on your current plan 2. **Credits the unused portion** back to your account 3. **Charges for the new plan** from the change date 4. **Generates a single invoice** with the net amount ### Example Scenario You have a monthly plan that costs `$100/month`, and you want to upgrade to a `$200/month` plan on day 15 of your billing cycle: * **Days used**: 15 days * **Days remaining**: 15 days * **Credit for unused time**: `$50` (50% of `$100`) * **Charge for new plan**: `$100` (50% of `$200`) * **Net amount due**: \$50 ## Proration Calculations ### Time-Based Proration Proration is calculated based on the time remaining in your current billing period: ``` Credit Amount = (Days Remaining / Total Days) × Current Plan Price Charge Amount = (Days Remaining / Total Days) × New Plan Price Net Amount = Charge Amount - Credit Amount ``` ### Billing Period Considerations * **Monthly Plans**: Proration based on days in the month * **Quarterly Plans**: Proration based on days in the quarter * **Yearly Plans**: Proration based on days in the year * **Leap Years**: February 29th is included in calculations ## Proration Examples ### Example 1: Monthly Upgrade **Current Plan**: Basic Monthly (`$50`/month) **New Plan**: Premium Monthly (`$100`/month) **Change Date**: Day 10 of 30-day month **Calculation:** * Days used: 10 * Days remaining: 20 * Credit: (20/30) × $50 = $33.33 * Charge: (20/30) × $100 = $66.67 * Net amount: $66.67 - $33.33 = \$33.34 ### Example 2: Quarterly Downgrade **Current Plan**: Premium Quarterly (`$300`/quarter) **New Plan**: Basic Quarterly (`$150`/quarter) **Change Date**: Day 45 of 90-day quarter **Calculation:** * Days used: 45 * Days remaining: 45 * Credit: (45/90) × $300 = $150.00 * Charge: (45/90) × $150 = $75.00 * Net amount: $75.00 - $150.00 = -\$75.00 (credit) ### Example 3: Yearly Plan Change **Current Plan**: Basic Yearly (`$600`/year) **New Plan**: Premium Yearly (`$1200`/year) **Change Date**: Day 100 of 365-day year **Calculation:** * Days used: 100 * Days remaining: 265 * Credit: (265/365) × $600 = $435.62 * Charge: (265/365) × $1200 = $871.23 * Net amount: $871.23 - $435.62 = \$435.61 # Upgrade & Downgrade Source: https://docs.flexprice.io/docs/subscriptions/upgrade-and-downgrade Learn how to upgrade or downgrade subscription plans with automatic proration and billing adjustments ## Overview Flexprice allows you to change subscription plans seamlessly with automatic proration calculations, billing adjustments, and invoice generation. Whether you're upgrading to a higher tier or downgrading to a more cost-effective plan, the system handles all the complex billing logic for you. ## Key Features * **Automatic Proration**: Calculate credits and charges for unused time * **Immediate Billing**: Generate invoices for plan changes instantly * **Preview Changes**: See the impact before making changes * **Flexible Billing**: Support for different billing cycles and periods * **Audit Trail**: Complete history of all subscription changes ## How It Works ### The Process 1. **Preview the Change**: Use the preview endpoint to see the financial impact 2. **Execute the Change**: Confirm the change to apply it immediately 3. **Automatic Processing**: The system handles proration, billing, and invoice generation 4. **Subscription Update**: Your subscription is updated to the new plan ### API Endpoints * [**Preview Change**](/api-reference/subscriptions/preview-subscription-plan-change) - See the financial impact before making changes * [**Execute Change**](/api-reference/subscriptions/execute-subscription-plan-change) - Apply the subscription plan change ## API Reference ### 1. Preview Subscription Plan Change **Endpoint:** `POST /subscriptions/{id}/change/preview` Preview the financial impact of changing a subscription plan before making the actual change. #### Request Fields | Field | Type | Required | Description | | ---------------------- | ------- | -------- | ------------------------------------------------------------------------------------- | | `target_plan_id` | string | ✅ | The ID of the new plan you want to change to | | `billing_cadence` | enum | ✅ | How often billing occurs: `RECURRING` or `ONETIME` | | `billing_cycle` | enum | ✅ | When billing cycles start: `anniversary` or `calendar` | | `billing_period` | enum | ✅ | Billing frequency: `MONTHLY`, `ANNUAL`, `WEEKLY`, `DAILY`, `QUARTERLY`, `HALF_YEARLY` | | `billing_period_count` | integer | ✅ | Number of billing periods for the new subscription | | `proration_behavior` | enum | ✅ | How to handle proration: `create_prorations` (default) or `none` | | `metadata` | object | ❌ | Additional key-value pairs for storing extra information | ### 2. Execute Subscription Plan Change **Endpoint:** `POST /subscriptions/{id}/change/execute` Execute the subscription plan change after previewing the impact. #### Request Fields Uses the same `SubscriptionChangeRequest` as the preview endpoint. ### Field Explanations #### Billing Configuration * **`billing_cadence`**: Whether this is a one-time charge (`ONETIME`) or recurring billing (`RECURRING`) * **`billing_cycle`**: * `anniversary`: Billing starts from the subscription start date * `calendar`: Billing aligns with calendar periods (e.g., monthly on the 1st) * **`billing_period`**: How often the customer is billed (daily, weekly, monthly, etc.) * **`billing_period_count`**: How many periods the subscription lasts (e.g., 12 for annual) #### Proration Behavior * **`create_prorations`**: Calculate and apply credits/charges for unused time * **`none`**: No proration ### What Happens During a Plan Change When you change your subscription plan, Flexprice: 1. **Archives the old subscription** and marks it as cancelled 2. **Calculates proration** for unused time on the current plan 3. **Creates a new subscription** with the target plan 4. **Generates an invoice** with credits and charges # View subscription Source: https://docs.flexprice.io/docs/subscriptions/view View a customer’s subscription details, active plans, and upcoming invoices. Once a customer is assigned a subscription, you can view their active plans and upcoming invoices. * In **Customer Management > Customers**, select the customer profile. * The **Subscriptions** section lists all active plans assigned to the customer. Screenshot 2025-03-12 at 4.25.15 PM.png * Clicking on a plan will open the **Subscription Details** page. * Here, you can view: * **Plan Name** * **Billing Period** * **Subscription Status** * **Start Date** * **Upcoming Invoice** * If the customer is on a usage-based plan, you can refresh the invoice to see real-time calculations based on their latest consumption. **View Upcoming Invoices** Screenshot 2025-03-12 at 4.28.30 PM.png * Whenever a customer starts using usage-based features, any additional charges associated with it will reflect in the upcoming invoice. * Click the **Refresh Icon** to update and view real-time billing calculations. # Pause subscription Source: https://docs.flexprice.io/docs/subscriptions/workflows/pause There are scenarios where customers may need to temporarily **pause their subscription** instead of canceling it. This feature ensures they are not billed during inactivity while allowing for a seamless reactivation when needed. Common use cases include: * **Seasonal Usage**: A customer only requires the service at certain times of the year. * **Budget Constraints**: A temporary pause allows cost control without losing their setup. * **Operational Downtime**: Businesses undergoing internal transitions can pause subscriptions until they resume operations. * **Trial Extension**: Giving users extra time before fully committing to a plan. With Flexprice, pausing a subscription **stops all billing for the selected period** while keeping the account intact. **Steps to Pause a Subscription:** * Select the customer whose subscription needs to be paused from Customers tab * Under the Subscriptions section, find the active subscription. * Click on the Options Menu (⋮) next to the subscription. Screenshot 2025-03-12 at 5.10.06 PM.png * Choose Pause Subscription from the dropdown. * Select the Pause Start Date and enter the number of days to pause the subscription. Screenshot 2025-03-12 at 5.10.44 PM.png * A confirmation message will summarize the pause period. * Click Schedule Pause to apply changes. Once paused, the subscription status updates to **Paused**, and the customer will not be charged until the subscription resumes. **Steps to Resume a Subscription:** A paused subscription can either **automatically resume** after the scheduled pause period or can be **resumed manually** at any time before the scheduled resume date. * Under **Subscriptions**, locate the paused subscription. * Click on the **Options Menu (⋮)** next to the subscription. * Select "Resume Subscription” Screenshot 2025-03-12 at 5.12.42 PM.png * A confirmation message appears, indicating that resuming will **restart the billing cycle**. * Click **Yes, Resume** to reactivate the subscription. Screenshot 2025-03-12 at 5.13.14 PM.png Once resumed, billing will continue based on the subscription’s original schedule. **Points to consider:** * Billing automatically resumes once the pause period ends unless manually resumed earlier. * For usage-based subscriptions, any usage before pausing will still be billed. * Customers using advance payment plans will be charged immediately upon resuming the subscription. # Trialing Source: https://docs.flexprice.io/docs/subscriptions/workflows/trialing Offer free trial periods on subscriptions before billing begins ## Overview A **trial** is a window where customers are on the paid plan schedule but **billing has not begun** yet in the recurring sense described below. **Where `trial_period_days` comes from** * Pass **`trial_period_days`** on **`POST /v1/subscriptions`** to override the plan for that subscription only. * **Or omit it.** Every recurring **fixed** price on the linked plan contributes the **same** **`trial_period_days`** (otherwise create fails validation). **Two invoices developers care about** * **Trial start (`SUBSCRIPTION_TRIAL_START`):** **`$0`**, finalized at subscribe time **with** the same **line-item shape** as real advance billing for **`trial_start` → `trial_end`**, but all amounts forced to **`0`** (preview + integrations). **`collection_method`** controls **`SUCCEEDED`** vs **`PENDING`** here; details in **[Trial-start invoice](#trial-start-invoice)**. * **Trial end (`SUBSCRIPTION_TRIAL_END`):** first payable billing window **`trial_end`** → **`trial_end` + `billing_period`**. **`payment_behavior`** drives failure handling here; details in **[What happens at trial end](#what-happens-at-trial-end)**. **Typical reasons to use trials** * Product-led growth and self-serve upgrade paths * Freemium-style evaluation before committing * Time-boxed enterprise or partner pilots ## How It Works Trial subscriptions move through three statuses: ```text theme={null} trialing -> (trial_end reached) -> incomplete -> (invoice paid / zero amount) -> active ``` | Status | What it means | Billing | | ------------ | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | `trialing` | Trial window is active | **`SUBSCRIPTION_TRIAL_START`**: **`$0`** finalized invoice for **`trial_start` → `trial_end`** (preview line items); subscription **stays** `trialing` | | `incomplete` | Trial ended; first paid invoice issued | Awaiting **`SUBSCRIPTION_TRIAL_END`** payment (unless amount is **`$0`**) | | `active` | Trial converted or non-trial start | Normal billing cycle | **Period fields while trialing** * **`current_period_start`** = **`trial_start`** * **`current_period_end`** = **`trial_end`** After **`trial_end`**, the billing anchor moves to **`trial_end`**. The first **paid** period is **full-length**: **`[trial_end, trial_end + billing_period]`**. There is **no shortened** first billing period after a trial. **Automation:** trial-end processing is handled by Flexprice workflows. **No manual cron or API** is required for the **`trial_end`** transition itself. ## Trial-start invoice **Billing reason (API string):** **`SUBSCRIPTION_TRIAL_START`**. Applies only when the subscription is **created as non-draft** **`trialing`** (trial window > 0 days). What you get on create: * **Invoice period:** `period_start` = `trial_start` (same as `start_date`), `period_end` = `trial_end`, matching **`current_period_*`** while **`subscription_status`** is **`trialing`**. * **Amounts:** **`subtotal`**, **`total`**, **`amount_due`**, and **each line item `amount`** are **`0`** after compute. Quantity, price linkage, and display metadata stay, so customers see **what will charge** later. * **Subscription status:** Remains **`trialing`**. **`SUBSCRIPTION_TRIAL_START` does not gate activation.** Moving to **`active`** still depends on **`SUBSCRIPTION_TRIAL_END`** (or a non-trial create path). ### `collection_method` and `payment_behavior` Use these fields deliberately: they clarify how **trial start** differs from **trial end**. | Topic | Role | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | **`payment_behavior`** | Applies when generating / paying **`SUBSCRIPTION_TRIAL_END`** and broader subscription invoice payment flows (`default_active`, `allow_incomplete`, …). See **[What happens at trial end](#what-happens-at-trial-end)**. | | **`collection_method`** | After finalize, **`$0`** trial-start invoices are **`SUCCEEDED`** under **`charge_automatically`** or left **`PENDING`** under **`send_invoice`** so outbound invoice sync can capture a card **without `active`**. | | `collection_method` | Trial-start invoice `payment_status` (typical) | | -------------------------- | ----------------------------------------------------------------- | | **`charge_automatically`** | **`SUCCEEDED`** at **`$0`** | | **`send_invoice`** | **`PENDING`** until host / PSP completes the synced **`$0`** flow | On **`POST /v1/subscriptions`**, **`latest_invoice`** is this document when **`subscription_status`** is **`trialing`**. ## Configuring a Trial Period 1. Define **`trial_period_days`** on the plan’s recurring **FIXED** prices **or** 2. Override on **`POST /v1/subscriptions`**. There is **no `trial_enabled` flag.** If **`trial_period_days` > 0** after resolve (inherit vs override), the subscription enters **`trialing`**; **`0`** at subscription level **disables trial** even if the plan carries trial days. ### Step 1: Set `trial_period_days` on the price **Constraints:** * Only **`billing_cadence: RECURRING`** + **`price_type: FIXED`** combinations accept **`trial_period_days`**. Usage-only or unsupported tier geometries fail validation upstream. * Omitted ⇒ **`0`**. That price contributes **no inherited trial**. ```bash theme={null} curl -X POST https://api.flexprice.io/v1/prices \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "amount": 4900, "currency": "USD", "billing_cadence": "RECURRING", "billing_period": "MONTHLY", "billing_period_count": 1, "price_type": "FIXED", "trial_period_days": 14 }' ``` Response (abbreviated): ```json theme={null} { "id": "price_01abc123", "billing_cadence": "RECURRING", "price_type": "FIXED", "trial_period_days": 14 } ``` ### Step 2: Create a subscription (trial inherited automatically) When all recurring fixed prices on a plan share the same `trial_period_days`, that value is inherited by the subscription automatically. No trial fields are required in the subscription request. ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "cust_01xyz", "plan_id": "plan_01abc", "currency": "USD", "billing_cadence": "RECURRING", "billing_period": "MONTHLY", "billing_period_count": 1, "start_date": "2025-05-01T00:00:00Z" }' ``` Response (abbreviated; real payloads also include **`plan`**, **`customer`**, line items with full pricing metadata, and other subscription fields): ```json theme={null} { "id": "sub_01def456", "subscription_status": "trialing", "customer_id": "cust_01xyz", "plan_id": "plan_01abc", "trial_start": "2025-05-01T00:00:00Z", "trial_end": "2025-05-15T00:00:00Z", "current_period_start": "2025-05-01T00:00:00Z", "current_period_end": "2025-05-15T00:00:00Z", "collection_method": "charge_automatically", "latest_invoice": { "id": "inv_trial_start_abc", "billing_reason": "SUBSCRIPTION_TRIAL_START", "invoice_type": "SUBSCRIPTION", "invoice_status": "FINALIZED", "payment_status": "SUCCEEDED", "currency": "USD", "period_start": "2025-05-01T00:00:00Z", "period_end": "2025-05-15T00:00:00Z", "subtotal": "0", "total": "0", "amount_due": "0", "amount_remaining": "0", "line_items": [ { "display_name": "Pro · monthly (trial preview)", "amount": "0", "quantity": "1" } ] } } ``` Notes on the abbreviated response: * **`subscription_status: "trialing"`** is the canonical lifecycle enum; the payload may also expose a separate **`status`** field (`SubscriptionResponse`). * **`latest_invoice.billing_reason`** is **`SUBSCRIPTION_TRIAL_START`** on this **`$0`** opening invoice. It stays **visible / finalized**, not **`SKIPPED`**, unlike typical zero-amount **`SUBSCRIPTION_CREATE`** invoices. * If you use **`send_invoice`** instead of **`charge_automatically`**, **`payment_status`** on that same invoice will usually be **`PENDING`** until PSP / outbound sync settles the **`$0`** path. ### Overriding trial days at subscription creation Pass `trial_period_days` in the subscription request to override the plan-level value. ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "cust_01xyz", "plan_id": "plan_01abc", "currency": "USD", "billing_cadence": "RECURRING", "billing_period": "MONTHLY", "billing_period_count": 1, "start_date": "2025-05-01T00:00:00Z", "trial_period_days": 0 }' ``` | Value | Effect | | ------- | ----------------------------------------------------------- | | Omitted | Inherits `trial_period_days` from plan prices | | `0` | Disables trial; subscription starts as `active` immediately | | `> 0` | Overrides plan-level value with this number of days | To shorten or lengthen the plan-level trial, pass a positive integer: ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "cust_01xyz", "plan_id": "plan_01abc", "currency": "USD", "billing_cadence": "RECURRING", "billing_period": "MONTHLY", "billing_period_count": 1, "start_date": "2025-05-01T00:00:00Z", "trial_period_days": 30 }' ``` ## What Happens at Trial End Trial end is detected automatically. When `trial_end <= now`: 1. Subscription status changes to `incomplete`. 2. Billing anchor resets to `trial_end`. 3. A `SUBSCRIPTION_TRIAL_END` invoice is created covering `[trial_end, trial_end + billing_period]`. 4. If the invoice amount is **zero**: subscription immediately converts to `active`. No payment event needed. 5. If the invoice amount is **non-zero**: subscription stays `incomplete` until the invoice is paid. An `incomplete` subscription does not auto-expire. It remains `incomplete` until the trial-end invoice is paid or the subscription is cancelled. **`payment_behavior` at trial end:** | Value | Behavior if payment fails | | -------------------------- | ---------------------------------------------------------------------------------- | | `default_active` (default) | Subscription activates regardless of payment result | | `allow_incomplete` | Subscription stays `incomplete` if payment fails | | `error_if_incomplete` | Subscription stays `incomplete` if payment fails; wallet fallback is not attempted | | `default_incomplete` | Subscription stays `incomplete`; only valid with `send_invoice` collection method | Credit grants held pending activation are applied automatically when the subscription becomes `active`. ## API Reference ### Price fields | Field | Type | Required | Description | | ------------------- | ------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `trial_period_days` | integer | No | Trial length inherited by subscriptions linking this recurring fixed price. Default `0` (no trial). Only valid for `billing_cadence: RECURRING` + `price_type: FIXED`. Must be `>= 0`. | ### Subscription create fields | Field | Type | Required | Description | | ------------------- | ------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `trial_period_days` | integer | No | Override plan-level trial length in days. `0` disables trial. Omit to inherit from plan prices (all recurring fixed plan prices must share the same value). Must be `>= 0`. | | `payment_behavior` | enum | No | **`SUBSCRIPTION_TRIAL_END`** and broader subscription payment semantics when attempts fail (`default_active`, `allow_incomplete`, `error_if_incomplete`, `default_incomplete`). API normalizes unspecified creates toward **`default_active`** (not all combinations are valid with every `collection_method`). | | `collection_method` | enum | No | **`charge_automatically`** vs **`send_invoice`**. For **`SUBSCRIPTION_TRIAL_START`**, controls whether the **`$0`** invoice is **`succeeded` immediately** or left **`pending`** for outbound invoicing/card capture. | ### Subscription response (trial-related fields) | Field | Type | Description | | ------------------------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------ | | `subscription_status` | string | **`trialing`** while the trial window is open. Prefer this for branching; **`status`** may also exist on **`SubscriptionResponse`**. | | `trial_start` | timestamp | Trial start (= **`start_date`** on create). | | `trial_end` | timestamp | When paid billing anchors; first **`SUBSCRIPTION_TRIAL_END`** period starts here. | | `current_period_start` | timestamp | During trial = **`trial_start`**. | | `current_period_end` | timestamp | During trial = **`trial_end`**. | | `latest_invoice` | object | On non-draft **`trialing`** creates, populated with **`SUBSCRIPTION_TRIAL_START`**. | | `latest_invoice.billing_reason` | string | **`SUBSCRIPTION_TRIAL_START`**, the **`$0`** trial preview invoice. | ## Webhooks & Events **Trial → paid conversion:** * **`subscription.activated`** when **`subscription_status`** flips **`trialing` → `active`**. * Raised after **`SUBSCRIPTION_TRIAL_END`** settles (**paid**) **or** auto-completes at **`amount_due = 0`** (no PSP event required). * **`SUBSCRIPTION_TRIAL_START`** emits **no** **`subscription.activated`**. **`subscription_status`** remains **`trialing`** until **`SUBSCRIPTION_TRIAL_END`** finishes, even if the opening invoice shows **`payment_status: SUCCEEDED`**. There is no dedicated **`subscription.trialing`**, **`subscription.trial_ended`**, or **`subscription.trial_ending`** webhook. Use **`subscription.activated`** (and your own trial clock if needed) to detect conversion. Example payload shape (**truncated**; your handler may receive more fields): ```json theme={null} { "subscription": { "id": "sub_01def456", "status": "active", "trial_start": "2025-05-01T00:00:00Z", "trial_end": "2025-05-15T00:00:00Z", "current_period_start": "2025-05-15T00:00:00Z", "current_period_end": "2025-06-15T00:00:00Z" } } ``` For information on configuring webhook endpoints, see [Webhooks](/docs/webhook/webhooks). ## Edge Cases | Scenario | Behavior | | ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | `trial_period_days = 0` at subscription level | Trial disabled even if plan price has `trial_period_days > 0` | | Plan prices have mismatched `trial_period_days` | Subscription creation fails: `"all recurring fixed plan prices must have the same trial_period_days"` | | Cancellation during trial | Standard cancellation applies. Trial-end processing skips subscriptions not in `trialing` status. | | Paused subscription when `trial_end` is reached | Trial-end processing is skipped. Runs when the subscription is unpaused. | | Zero-amount trial-end invoice | Subscription auto-activates immediately without waiting for a payment event. | | Trial-start **`$0`** invoice | Always created for **`trialing`** creates; **`SUBSCRIPTION_TRIAL_START`** stays visible (not **`SKIPPED`**) unlike other **`$0`** subscription invoices | | Inherited (child) subscriptions | Trial status cascades from parent. Children are not processed independently. | | Re-trialing | Not supported. Each subscription has one trial window. To extend a trial, cancel and recreate the subscription. | ## Best Practices * **Payment ahead of trial end.** You can onboard trialing subscriptions without storing a card, but **`SUBSCRIPTION_TRIAL_END`** still expects a collectible path whenever **`amount_due > 0`**. * **`send_invoice` + outbound PSP sync.** Keeps **`SUBSCRIPTION_TRIAL_START`** in **`PENDING`** until the hosted **`$0`** handshake finishes. Prefer this pattern when the gateway captures cards solely behind invoice artifacts. * **Pin `payment_behavior` in production.** **`SUBSCRIPTION_TRIAL_END`** obeys it when autopay retries fail (**`allow_incomplete`** vs **`default_active`**, etc.). * **Split entitlements cleanly.** Fire **`subscription.activated`** once **`SUBSCRIPTION_TRIAL_END`** succeeded (or **`$0`** short-circuit). **`SUBSCRIPTION_TRIAL_START`** leaves **`subscription_status`** at **`trialing`** even when **`payment_status`** reads **`SUCCEEDED`**. * **Cheap staging loops.** Truly **`$0`** plan prices accelerate **`trialing → active`** because **`SUBSCRIPTION_TRIAL_END`** can finalize with nothing to capture. * **Single trial episode.** No built-in replay; cancel + recreate is the sanctioned reset. Configure webhook destinations in **[Webhooks](/docs/webhook/webhooks)** (events: **[Webhooks & Events](#webhooks--events)** above). # Auto Top-Up Source: https://docs.flexprice.io/docs/wallet/auto-top-up Automatically recharge customer wallets when balance falls below a configured threshold to ensure uninterrupted service. The Auto Top-Up feature in Flexprice automatically adds credits to a customer's wallet when the ongoing balance falls below a predefined threshold. This ensures continuous service availability and eliminates the need for manual monitoring and intervention. **Benefits of Auto Top-Up:** * **Uninterrupted Service**: Prevents service disruptions due to insufficient wallet balance * **Automated Management**: Eliminates manual monitoring and top-up operations * **Improved Cash Flow**: Ensures timely wallet replenishment * **Flexible Configuration**: Choose between immediate credits or invoiced top-ups * **Proactive Balance Management**: Maintains wallet balance above critical thresholds ## How Auto Top-Up Works Auto top-up monitors the **ongoing balance** (real-time credit balance) of a wallet, which accounts for: * Current wallet credit balance * Pending invoice amounts * Current period usage When the ongoing balance drops to or below the configured threshold, Flexprice automatically triggers a top-up operation if configured, based on your configuration. ## Auto Top-Up Modes Flexprice supports two modes for auto top-up: ### 1. Direct Top-Up Credits are immediately added to the wallet when the threshold is reached. This mode provides instant balance replenishment. **When to Use:** * When you want immediate credit availability * For customers with reliable payment methods * When you need to prevent any service interruption ### 2. Invoiced Top-Up An invoice is created when the threshold is reached, and credits are only added to the wallet after the invoice is paid. This mode provides better cash flow control. **When to Use:** * When you want payment confirmation before adding credits * For customers who prefer invoiced billing * When you need to track top-up transactions separately ## Configuring Auto Top-Up ### During Wallet Creation You can enable auto top-up when creating a new wallet: Go to **Billing** → **Customers** → Select customer → **Wallet** tab Click the **three-dot menu (⋮)** in the top right corner of the wallet section Select **Auto Topup** from the dropdown menu to enable auto top-up Access Wallet Settings Toggle the **Enable Auto Top-Up** switch to activate automatic wallet recharging Enable Auto Top-Up Configure the following settings: * **Threshold**: Enter the minimum ongoing balance amount that triggers auto top-up * **Top-Up Amount**: Enter the number of credits to add when triggered * **Invoicing Mode**: Toggle the switch to choose between: * **Direct Credit**: Credits added immediately * **Invoiced**: Invoice created, credits added after payment Configure Auto Top-Up Click **Save Changes** to apply the auto top-up configuration ### Updating Auto Top-Up Settings You can modify auto top-up configuration for existing wallets: Go to **Billing** → **Customers** → Select customer → **Wallet** tab Click the **three-dot menu (⋮)** in the top right corner of the wallet section Select **Auto Topup** from the dropdown menu to enable auto top-up Configure the following settings: * **Threshold**: Enter the minimum ongoing balance amount that triggers auto top-up * **Top-Up Amount**: Enter the number of credits to add when triggered * **Invoicing Mode**: Toggle the switch to choose between: * **Direct Credit**: Credits added immediately * **Invoiced**: Invoice created, credits added after payment Configure Auto Top-Up Click **Save Changes** to apply the auto top-up configuration **Note**: Partial updates are supported. You can update only specific fields without changing others. ## Auto Top-Up Configuration Options ### Threshold The minimum ongoing balance that triggers auto top-up. When the ongoing balance falls to or below this value, the top-up is automatically triggered. ### Top-Up Amount The number of credits to add to the wallet when auto top-up is triggered. ### Invoicing Mode Determines how credits are added: * **Direct Credit** (`invoicing: false`): Credits are immediately added to the wallet * **Invoiced** (`invoicing: true`): An invoice is created, and credits are added after payment successful ## Auto Top-Up Workflow ### Direct Top-Up Flow When `invoicing: false` is configured, credits are added immediately: The wallet's ongoing balance falls to or below the configured threshold value Flexprice automatically detects the low balance and triggers the top-up process Credits are immediately added to the wallet balance A wallet transaction is created with status `completed` and transaction reason `PURCHASED_CREDIT_DIRECT` The wallet balance is updated instantly, and the transaction appears in the wallet transaction history ### Invoiced Top-Up Flow When `invoicing: true` is configured, an invoice is created and credits are added after payment: The wallet's ongoing balance falls to or below the configured threshold value Flexprice automatically detects the low balance and triggers the top-up process An invoice is created with status `pending` and linked to the wallet transaction A wallet transaction is created with status `pending` and transaction reason `PURCHASED_CREDIT_INVOICED` The wallet balance remains unchanged until the invoice is paid When the invoice is paid: * Transaction status changes to `completed` * Credits are added to the wallet * Wallet balance is updated **Note**: If your account has `AutoCompletePurchasedCreditTransaction` enabled, invoiced top-ups may complete immediately without waiting for payment. ## Use Cases ### SaaS Companies * **Usage-Based Billing**: Automatically top up wallets when usage depletes balance * **Prepaid Plans**: Maintain minimum balance for prepaid customers * **Service Continuity**: Prevent service interruptions for high-value customers ### API Providers * **API Usage**: Maintain wallet balance for API consumption * **Rate Limiting**: Prevent service disruption due to insufficient credits * **Enterprise Customers**: Provide automated balance management for enterprise accounts ## Best Practices 💡 **Set Realistic Thresholds**: Choose threshold values that provide sufficient buffer before balance reaches zero. Consider typical usage patterns and top-up frequency. 💡 **Match Top-Up Amount to Usage**: Set top-up amounts that align with typical customer usage. Too small may trigger frequently; too large may tie up unnecessary funds. 💡 **Consider Invoicing Mode**: Use direct top-up for trusted customers with reliable payment methods. Use invoiced mode when you need payment confirmation before adding credits. 💡 **Monitor Transaction History**: Regularly review auto top-up transactions to understand patterns and optimize configuration. 💡 **Combine with Low Balance Alerts**: Use auto top-up alongside low balance alerts for comprehensive wallet management. ## Troubleshooting ### Auto Top-Up Not Triggering **Possible Causes:** * Auto top-up is disabled * Ongoing balance is above threshold **Solutions:** * Verify auto top-up is enabled in wallet settings * Check real-time balance vs. threshold value ### Credits Not Added (Invoiced Mode) **Possible Causes:** * Invoice is still pending payment * Invoice payment failed * Auto-complete setting is disabled **Solutions:** * Check invoice payment status * Verify invoice payment was successful * Review transaction status in wallet history ### Frequent Top-Ups **Possible Causes:** * Threshold set too high * Top-up amount too small * High usage depleting balance quickly **Solutions:** * Adjust threshold to a lower value * Increase top-up amount * Review customer usage patterns ## Related Documentation * [Wallet Transactions](/docs/wallet/transactions) - Track and monitor all wallet transactions * [Top-Up Wallet](/docs/wallet/top-up) - Learn about manual wallet top-ups * [Low Balance Alert](/docs/wallet/low-balance-alert) - Configure balance alerts # Conversion rate Source: https://docs.flexprice.io/docs/wallet/conversion-rate Conversion rate links your unit (e.g. credits) to base currency (e.g. USD). Billing stays in one currency. ## Overview ### What is wallet conversion rate? **Wallet conversion rate** tells you how much money one credit is worth in your wallet's currency. * The wallet **stores** balance in **credits** only. Currency is used only for **calculation and display**. Formula: **amount in currency = credits × conversion rate**. * **Example:** Conversion rate **0.01**, wallet in USD. Then 1 credit = 0.01 USD. So 100 credits = 1 USD, and 1000 credits show as a 10 USD balance. * You can set **two** rates on a wallet: * **Conversion rate** — used when you **spend** (debit) and when balance is shown in currency. * **Top-up conversion rate** — used only when you **add** credits (top-up). If you don’t set it, top-ups use conversion rate. *** ### What is conversion rate? It is the number that links your unit to base currency. It appears in **two** places: | Field | Purpose | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `conversion_rate` | Links stored credits to currency (for display and when user spends). Wallet stores balance in **credits**; currency is worked out from this. Formula: `amount in currency = credits × conversion_rate`. Default: `"1"`. | | `topup_conversion_rate` | Same formula; used **only for top-ups**. If you do not set it, the wallet uses `conversion_rate` for top-ups. | So: **1 credit = conversion\_rate in currency.** Examples: rate 1 → 1 USD = 1 credit; rate 2 → 1 USD = 0.5 credits; rate 0.5 → 1 USD = 2 credits. *** ## How it works internally * **Price unit:** Has `base_currency` and `conversion_rate`. Billing converts price unit amounts into that base currency for invoicing. * **Wallet:** Balance is stored in **credits**. To show it in currency: amount = credits × conversion rate. ### When conversion is used Uses `topup_conversion_rate` (or `conversion_rate` if not set). If you send `amount` and not `credits_to_add`, credits = amount ÷ rate. If both are sent, **credits\_to\_add** wins and amount is ignored. Uses `conversion_rate` (e.g. invoice payment). Manual debit uses `credits` directly; no conversion. Uses conversion\_rate: currency = credits × rate; credits = currency ÷ rate. *** ## Conversion during credit Top-up uses **topup\_conversion\_rate**. If not set, it uses **conversion\_rate**. Send `amount` (in wallet currency). System works out `credits_to_add = amount ÷ topup_conversion_rate` and adds that many credits to the wallet. Send `credits_to_add`. That many credits are added to the wallet. Wallet in USD, conversion rate (1 credit = 2 USD). Top-up with amount 10 USD → credits added = 10 ÷ 2 = 5. Wallet balance goes up by 5 credits. *** ## Conversion during debit Debits use **conversion\_rate**. Not the top-up rate. Amount in currency is turned into credits: `credits = amount ÷ conversion_rate`. That many credits are taken out. Send `credits`. No conversion. That many credits are taken out. Same wallet (rate 2). Debit 5 credits → 10 USD worth taken out (5 × 2 = 10). *** ## Configuration ### Set conversion rate on a wallet Go to **Customers** → select the customer → **Wallets** tab. Create Price Unit Click **Create Wallet** (or **+ Add**). For **Custom** currency, select a price unit — **Currency** and **Conversion rate** are set from that unit. For fiat, enter **Conversion rate** and optionally **Top-up conversion rate**. Create Price Unit ### Set conversion rate via API `POST /v1/wallets` — send `currency` (required), `conversion_rate` (optional, default `"1"`), `topup_conversion_rate` (optional), or `price_unit` (then currency and conversion\_rate come from the price unit). **Create wallet with explicit rates:** ```json theme={null} { "customer_id": "cust_xxx", "currency": "usd", "conversion_rate": "0.01", "topup_conversion_rate": "0.01" } ``` `POST /v1/wallets/{id}/top-up` — send `amount` (converted with top-up rate) or `credits_to_add`; if both, credits are used. **Top-up by amount** (uses top-up rate): ```json theme={null} { "amount": "10.00", "transaction_reason": "PURCHASED_CREDIT", "idempotency_key": "topup-uniq-123" } ``` ### Validation * **Wallet:** `conversion_rate` defaults to `"1"`. If you set it, it must be > 0. `topup_conversion_rate` must be > 0 if you set it. When you use `price_unit`, that price unit must be Active. *** ## Use cases Show and charge in credits. Invoice in one currency (e.g. USD). Set `topup_conversion_rate` so users get more credits per 1 USD. `conversion_rate` is still used for spending and for showing balance. Set **Top-up conversion rate** lower than **Conversion rate** so 1 USD buys more credits when users top up. Spending and balance still use **Conversion rate**. Example: conversion rate 0.01, top-up rate 0.008 → 1 USD = 125 credits on top-up. *** ## Edge cases | Case | What happens | | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | | **Missing or zero rate** | Price unit: request fails. Wallet: `conversion_rate` can default to `"1"`. Best: send a valid rate or use `price_unit`. | | **Invalid or negative rate** | Request fails. Rate must be > 0. | | **Top-up: amount and credits both sent** | **credits\_to\_add** is used. **amount** is ignored. | | **Wallet with price\_unit** | `conversion_rate` in the request is ignored. Currency and rate come from the price unit. | *** ## Examples `conversion_rate = "0.01"`. So 1 credit = 0.01 USD. Top-up 10 USD (same top-up rate) adds 1000 credits. Manual debit 500 credits takes out 5 USD worth. `conversion_rate = "0.01"`, `topup_conversion_rate = "0.008"`. Normal spend: 1 credit = 0.01 USD. Top-up: 1 USD = 125 credits. *** # Creating a wallet Source: https://docs.flexprice.io/docs/wallet/create A wallet in Flexprice acts as a virtual account linked to a customer. It allows for storing and managing credits that can be applied toward subscription payments, usage-based charges, or other transactions. Flexprice enable you to manage prepaid credits for your customers. These are credits added to a wallet and can be consumed for billing purposes. Wallets ensure an efficient and transparent mechanism for credit handling, aligning with subscription and usage-based pricing models. Wallets operate in a defined currency and can be topped up manually or automatically. **Creating a Wallet** * Go to the **Customers** section in the dashboard. * Select the desired customer. * Navigate to the **Wallet** tab in the customer’s profile. * If no wallet exists for the customer, you will see a message: *“No wallet linked to the customer yet.”* Screenshot 2025-03-12 at 5.37.58 PM.png * Click the **Add Wallet** button to open the wallet creation form. * Fill out the following details: Screenshot 2025-03-12 at 5.40.07 PM.png * **Wallet Name**: Assign a name to the wallet. By default, it will be “Prepaid Wallet.” * **Currency**: Select the currency in which this wallet will operate (e.g., USD, INR). Note: Only **one active wallet per currency** is allowed for a customer. If a wallet for the selected currency already exists, you must deactivate it before creating a new one. * Currently, only **Free Credits** can be added. These credits do not generate an invoice, as they are granted without charge. Also, the credit-to-currency ratio is **1:1** (e.g., 1 credit equals 1 USD or 1 INR) * Click **Save Wallet** to finalise the creation process. * The wallet will now appear in the Wallet tab for the customer, showing the balance and available credits. Screenshot 2025-03-12 at 5.40.54 PM.png # Low Balance Alert Source: https://docs.flexprice.io/docs/wallet/low-balance-alert Configure Wallet Sentinel Alerts to get notified when a customer's wallet balance crosses configurable Info, Warning, or Critical thresholds. Wallet Sentinel Alerts in Flexprice let you monitor wallet balance with **three alert levels** so you can act before balance becomes critical. You can enable or disable alerts per wallet and set a custom threshold and trigger condition for each level. **Benefits:** * **Proactive management** — Get notified at Info and Warning levels before balance reaches critical * **Granular control** — Separate thresholds for informational, warning, and critical states * **Flexible conditions** — Trigger when balance is above or below a value (e.g. "below" for low balance) * **Per-wallet settings** — Enable or disable alerts and configure thresholds per wallet ## Alert Levels | Level | Purpose | | ------------ | ------------------------------------------------------------ | | **Info** | Informational alert when balance crosses a defined threshold | | **Warning** | Early warning level before critical | | **Critical** | Critical alert level requiring immediate action | Each level can be configured with: * A **threshold value** (in the wallet’s currency, e.g. USD) * A **trigger condition** (e.g. **below** — alert when balance is below the threshold). Flexprice supports two conditions for thresholds: **Below** and **Above**. Alerts are turned on or off for a wallet using **Enable Alerts** (`alert_enabled` in the API). ## Configuring Wallet Sentinel Alerts * Go to **Billing** in the main navigation * Select **Customers**, then the desired customer * Open the **Wallet** tab in the customer profile * Click the **three-dot menu (⋮)** in the top right corner of the wallet section * Select **Alert Settings** from the dropdown Access Alert Settings In the **Wallet Alert Settings** dialog, turn **Enable Alerts** **on** to activate monitoring against your configured thresholds Enable Alert Settings * Set **Critical**, **Warning**, and **Info** thresholds. For each level set **Threshold Value** and **Condition** (e.g. **Below** — alert when balance is below the threshold). * Use **Remove** to drop a level you don't need. * Click **Save Changes** to apply. Configure Alert Settings ## Alert Behavior Once configured and enabled, the sentinel will: * **Monitor balance** — Evaluate wallet balance against your thresholds (e.g. after top-ups, debits, and accounting for usage) * **Trigger by level** — Raise **Info**, **Warning**, or **Critical** alert based on which threshold is crossed * **Update state** — Reflect current state (e.g. ok, info, warning, in\_alarm) on the wallet and in alert history * **Notify** — Send webhooks when alert state changes Webhooks are sent when alert state changes (e.g. ok → warning, warning → in\_alarm). Configure your webhook endpoint to receive these notifications. ## Managing Alert Settings ### Viewing Current Configuration Open the customer’s **Wallet** tab, click the **three-dot menu (⋮)** → **Alert Settings**. The dialog shows **Enable Alerts** and all threshold levels (Critical, Warning, Info) with their values and conditions. ### Changing Thresholds or Condition In the **Alert Settings** dialog box, adjust **Enable Alerts**, any threshold value, or condition per level, then click **Save Changes** to apply. ### Disabling Alerts Set **Enable Alerts** to **off** in the **Alert Settings** dialog and click **Save Changes** (or set `alert_enabled` to `false` via API). The wallet will no longer generate real time balance alerts until you enable them again. ## Use Cases ### SaaS and Usage-Based Billing * **Low balance warnings** — Info at 5, Warning at 0, Critical below 0 so you can top up before going negative * **Prepaid credits** — Notify when credits are running low at multiple levels to avoid running out of credits unexpectedly * **Service Continuity**: Prevent service interruptions due to insufficient funds ### E-commerce Platforms * **Wallet monitoring** — Different severities for different teams or workflows * **Promotional or prepaid wallets** — Alerts when balance is low relative to typical usage * **Payment Processing**: Ensure sufficient funds for transactions ### API and Credit-Based Products * **Usage vs balance** — Early Info/Warning so support or automation can act before Critical * **Multi-wallet** — Per-wallet sentinel settings so each wallet has its own thresholds * **Rate Limiting**: Prevent service disruption due to insufficient credits * **Customer Support**: Proactive customer communication ## Best Practices 💡 **Set realistic thresholds** — Choose values that give enough time to top up (e.g. Warning above zero if you want to avoid negative balance) 💡 **Use all three levels** — Use Info for early notice, Warning for action soon, Critical for immediate action 💡 **Match condition to goal** — Use **below** for “low balance” alerts; use **above** if you want alerts when balance exceeds a cap 💡 **Review periodically** — Adjust thresholds based on usage patterns and top-up behavior 💡 **Combine with auto top-up** — Use sentinel alerts together with auto top-up so critical levels can trigger automatic top-up when configured # Manual Debit Source: https://docs.flexprice.io/docs/wallet/manual-debit Manually deduct credits from customer wallets for refunds, adjustments, or administrative corrections. The Manual Wallet Debit feature allows you to manually deduct credits from a customer's wallet. This is essential for handling refunds, billing corrections, chargebacks, and other administrative adjustments. **Common Use Cases:** * **Refunds**: Process customer refunds for overcharges or billing errors * **Billing Adjustments**: Correct incorrect credit allocations * **Chargebacks**: Handle payment disputes and chargebacks * **Administrative Corrections**: Fix manual entry errors or system issues * **Wallet Termination**: Deduct remaining balance when closing a wallet ## How Manual Debit Works When you debit a wallet, Flexprice automatically: 1. **Validates Balance**: Ensures sufficient credits are available before processing 2. **Consumes Credits**: Deducts credits using a priority-based algorithm (see below) 3. **Updates Balance**: Reduces the wallet balance by the debited amount 4. **Creates Transaction Record**: Maintains a complete audit trail 5. **Triggers Alerts**: Notifies you if the balance falls below configured thresholds ## Credit Consumption Algorithm Flexprice uses a smart **Priority-Based FIFO** algorithm to determine which credits to consume when processing a debit: ### Consumption Order Credits are consumed in the following order: 1. **Priority** (Lower number = Higher priority) * Credits with priority 1 are consumed before priority 2 * Credits without priority are consumed last 2. **Expiry Date** (Earlier expiry first) * Credits expiring sooner are consumed first to prevent waste * Credits without expiry dates are consumed last 3. **Credit Amount** (Larger amounts first) * Among credits with the same priority and expiry, larger amounts are consumed first * This optimizes the number of transaction updates required ### Example Scenario Suppose a wallet has the following credits: | Credits | Priority | Expiry Date | Status | | ------- | -------- | ----------- | --------- | | 50 | 1 | 2024-03-01 | Available | | 30 | 1 | 2024-03-01 | Available | | 100 | 1 | 2024-03-15 | Available | | 75 | 2 | 2024-02-20 | Available | | 200 | None | None | Available | **If you debit 150 credits**, they will be consumed in this order: 1. **50 credits** from priority 1, expiring 2024-03-01 2. **30 credits** from priority 1, expiring 2024-03-01 3. **70 credits** from priority 1, expiring 2024-03-15 (partial) The wallet will have 30 credits remaining from the third transaction, plus the untouched 75 and 200 credits. ## Making a Manual Debit via Dashboard You can easily debit credits from a customer's wallet through the Flexprice dashboard: ### Step 1: Navigate to the Wallet * Go to **Billing** in the main navigation * Select **Customers** from the billing section * Select the customer whose wallet you want to debit * Navigate to the **Wallet** tab ### Step 2: Access Manual Debit * Click the **three-dot menu (⋮)** in the top right corner of the wallet section * Select **Manual Debit** from the dropdown menu Manual Debit Menu ### Step 3: Enter Debit Details A "Manual Debit" dialog will appear with the following fields: Manual Debit Form * **Credits to Deduct**: Enter the number of credits you want to debit from the wallet * The system will show a preview: "\$X will be debited from the wallet" * **Reference ID (Optional)**: Enter a unique reference ID for this transaction * This serves as the idempotency key to prevent duplicate debits * Useful for linking to invoices, tickets, or other reference systems * Click **Submit** to process the debit ### Step 4: Verify the Transaction After submitting: * The wallet balance will be updated immediately * The debit transaction will appear in the Transactions list * You'll see "-X credits" in the transaction history ## Making a Manual Debit via API ### Endpoint ```http theme={null} POST https://api.cloud.flexprice.io/v1/wallets/{wallet_id}/debit ``` ### Request Parameters | Parameter | Type | Required | Description | | -------------------- | ------ | -------- | ----------------------------------------------------------- | | `credits` | string | Yes | Amount of credits to debit (decimal format, e.g., "100.50") | | `transaction_reason` | string | Yes | Reason code for the debit (see Transaction Reasons below) | | `idempotency_key` | string | Yes | Unique key to prevent duplicate debits | | `description` | string | No | Human-readable description of the debit | | `metadata` | object | No | Custom key-value pairs for additional context | ### Transaction Reasons Use appropriate transaction reason codes to categorize debits: | Reason Code | Use Case | | ---------------------- | ------------------------------------------------- | | `MANUAL_BALANCE_DEBIT` | Administrative corrections and manual adjustments | ### Example Request ```bash theme={null} curl --request POST \ --url https://api.cloud.flexprice.io/v1/wallets/{id}/debit \ --header 'Content-Type: application/json' \ --header 'x-api-key: ' \ --data '{ "credits": 123, "description": "", "idempotency_key": "", "metadata": {}, "transaction_reason": "MANUAL_BALANCE_DEBIT" }' ``` ### Success Response ```json theme={null} { "id": "wallet_0123", "customer_id": "cust_0123", "name": "Prepaid Wallet", "currency": "usd", "balance": "1587", "credit_balance": "1587", "wallet_status": "active", "auto_topup_trigger": "disabled", "auto_topup_min_balance": "0", "auto_topup_amount": "0", "wallet_type": "PRE_PAID", "config": { "allowed_price_types": [ "ALL" ] }, "conversion_rate": "1", "alert_enabled": true, "alert_state": "ok", "created_at": "2025-11-05T13:41:59.256406Z", "updated_at": "2025-11-14T15:55:38.19095Z" } ``` ### Error Responses | Error Code | HTTP Status | Description | | ------------------------- | ----------- | ---------------------------------- | | `INSUFFICIENT_BALANCE` | 400 | Wallet doesn't have enough credits | | `INVALID_CREDITS` | 400 | Credits amount must be positive | | `MISSING_IDEMPOTENCY_KEY` | 400 | Idempotency key is required | | `WALLET_NOT_FOUND` | 404 | Wallet doesn't exist | | `WALLET_NOT_ACTIVE` | 400 | Wallet is not in active status | ### Example Error Response ```json theme={null} { "error": { "code": "INSUFFICIENT_BALANCE", "message": "insufficient balance", "hint": "Insufficient balance to process debit operation", "details": { "wallet_id": "wallet_abc123", "amount": "150.50", "available_balance": "100.00" } } } ``` ## Idempotency The `idempotency_key` parameter is **required** and ensures that duplicate API requests (e.g., due to network timeouts) don't result in multiple debits. ### Best Practices for Idempotency Keys * Use a **unique identifier** for each logical debit operation * Include context in the key, e.g., `refund_invoice_123_20240115` * **Never reuse** idempotency keys across different operations ## Use Case Examples ### Example: Administrative Correction ```bash theme={null} curl --location 'https://api.cloud.flexprice.io/v1/wallets/{id}/debit' \ --header 'Content-Type: application/json' \ --header 'Accept: application/json' \ --header 'x-api-key: sk_xxxxxxxxxxxx' \ --data '{ "idempotency_key": "Manual-adjustment-123", "transaction_reason": "MANUAL_BALANCE_DEBIT", "credits": 1 }' ``` ## Important Considerations ### Balance Validation Before processing a debit, Flexprice: * Checks that the wallet has sufficient **available credits** * Excludes expired credits from the calculation * Returns an error if insufficient balance is found ### Transaction Atomicity All debit operations are **atomic**: * Credits are consumed and wallet balance is updated in a single database transaction * If any step fails, all changes are rolled back * This ensures data consistency and prevents partial debits ### Audit Trail Every manual debit creates a complete audit trail: * Transaction record with type `DEBIT` * Before and after balances * Description and metadata * Transaction reason * Idempotency key for tracking * Timestamp and user information ### Alert Integration After a debit, Flexprice automatically: * Checks the wallet's low balance alert threshold * Triggers an alert if the new balance is below the threshold * Notifies you through configured alert channels ## Best Practices 💡 **Always Use Idempotency Keys**: Protect against duplicate debits by using unique idempotency keys for each operation 💡 **Provide Clear Descriptions**: Include descriptive text and metadata to make audit trails meaningful 💡 **Choose Appropriate Reasons**: Use the correct transaction reason code to categorize debits properly 💡 **Validate Before Debiting**: Check wallet balance before attempting large debits to avoid errors 💡 **Handle Errors Gracefully**: Implement proper error handling, especially for `INSUFFICIENT_BALANCE` errors 💡 **Monitor Alert Notifications**: Watch for low balance alerts after processing debits ## Troubleshooting ### Insufficient Balance Error **Problem**: API returns `INSUFFICIENT_BALANCE` error **Solution**: * Check the wallet's current balance before attempting the debit * Verify that you're not including expired credits in your calculation * Consider splitting large debits across multiple operations if needed ### Duplicate Idempotency Key Error **Problem**: API rejects request due to duplicate idempotency key **Solution**: * Ensure each unique debit operation has a unique idempotency key * Check if the previous operation with the same key completed successfully * Generate new idempotency keys for each distinct operation ### Wallet Not Active Error **Problem**: API returns `WALLET_NOT_ACTIVE` error **Solution**: * Verify the wallet status is `ACTIVE` * Check if the wallet has been closed or suspended * Reactivate the wallet if necessary before debiting ## Related Features * [Creating a Wallet](/docs/wallet/create) - Learn how to create wallets for customers * [Top-up Wallet](/docs/wallet/top-up) - Add credits to customer wallets * [Low Balance Alert](/docs/wallet/low-balance-alert) - Configure balance threshold alerts * [Prepaid and Promotional Credits](/docs/wallet/prepaid-and-promotional) - Understand credit types * [Wallet Transactions](/docs/wallet/transactions) - Track and monitor all wallet transactions # Prepaid & Promotional credits Source: https://docs.flexprice.io/docs/wallet/prepaid-and-promotional Description of your new file. Flexprice provides a flexible credit system that allows businesses to configure **prepaid and promotional credits** for customers. These credits can be used to offset recurring charges, usage-based charges, or both, depending on how the credit wallet is configured. Credits are a powerful tool in pricing and customer retention. They allow businesses to: * **Offer promotional incentives** → Free trial credits, signup bonuses, or customer loyalty rewards. * **Enable prepaid usage models** → Let customers buy credits upfront instead of committing to a fixed plan. images/Screenshot2025-03-16at6.46.23PM.png **Defining usage of charges** When a customer has available credits, Flexprice will **automatically deduct credits** based on the applicable charges: 1. **Recurring charges** * If the wallet allows **subscription-only credits**, they will be used to pay for the customer’s recurring plan. * If the credits are not enough, the remaining balance will be charged via the default payment method. 2. **Usage-based charges** * If the wallet allows **usage-based credits**, they will be used before any other payment method. * If usage exceeds the available credits, standard billing rules apply (e.g., pay-per-use pricing). 3. **Both subscription & usage-based charges** * If the wallet allows **both types of charges**, credits will be used wherever applicable. # Top-up wallet Source: https://docs.flexprice.io/docs/wallet/top-up Wallet top-ups allow you to add credits to customer wallets, ensuring continuous service availability. Flexprice supports two top-up methods: **manual top-up** for on-demand credit additions and **automatic top-up** for maintaining minimum balance thresholds. *** ## Manual Top-Up Manual top-ups provide flexibility to add credits on demand. You can issue free credits for promotions and compensations, or process purchased credits through standard payment methods. ### Adding Credits Manually Navigate to the customer's wallet and click **Top-up Wallet** in the top right corner Screenshot 2025-03-12 at 6.19.40 PM.png In the top-up dialog, select your credit type and enter the amount to add to the wallet Screenshot 2025-03-12 at 6.20.01 PM.png Click **Add Credits** to complete the top-up *** ## Automatic Top-Up Automatic top-up ensures uninterrupted service by automatically recharging wallets when they fall below a specified threshold. This eliminates manual monitoring and prevents service disruptions due to insufficient balance. ### Configuring Auto Top-Up During wallet creation, toggle **Recharge Wallet Automatically** Define when auto top-up should activate (e.g., when balance falls below \$100) Enter the minimum balance threshold that triggers automatic recharge Specify the amount to add when auto top-up is triggered (e.g., $100, $500) Click **Save changes** to activate auto top-up Screenshot 2025-03-12 at 6.20.01 PM.png *** ## Credit Types Supported Flexprice supports multiple credit types to accommodate different billing scenarios: ### Free Credits Top-Up Promotional or complimentary credits added without requiring payment. **Common use cases:** * Welcome bonuses for new customers * Promotional campaigns and special offers * Service credits for issue resolution * Customer loyalty and retention programs ### Purchased Credits Top-Up Purchased credit top-up refers to credits that a customer buys. We support two types: * **Purchased Credits** - Immediate credit addition * **Purchased Credits (With Invoice)** - Invoice-based top-up with payment tracking *** ## Purchased Credit Top-Up (with Invoice) The invoice-based top-up workflow provides comprehensive payment tracking and record-keeping for credit purchases. ### How It Works When you select **Purchased Credits** and enable **Generate Invoice**, Flexprice initiates the following flow: You select **Purchased Credits** and enable **Generate Invoice** in the top-up dialog, then click **Add Credits** An invoice is automatically generated and finalized with payment status `Pending`. The invoice contains the credit purchase details and is visible in your invoice list A wallet transaction is created with status `pending` and transaction reason `PURCHASED_CREDIT_INVOICED`. The transaction is linked to the generated invoice Screenshot 2025-03-12 at 6.20.01 PM.png Credits remain unavailable and are not added to the wallet balance. The wallet balance remains unchanged until payment is confirmed Credits get available only after the payment is followed through. When the invoice payment status becomes `Succeeded`, the wallet balance gets topped-up. Screenshot 2025-03-12 at 6.20.01 PM.png You can either `Record Payment` or `Update Payment Status` directly to confirm payment. * **Record Payment** - A payment record is created for the invoice supporting tracking and reconciliation. Payment can be recorded through following methods: * **Offline Payment** - Record payment that was processed outside the system. * **Payment Link** - Generate a payment link for online payment. Payment status gets updated when payment is successfully processed through the payment gateway. If you want to know more about the `payment_link` behavior, check out [Stripe Payment Link & Checkout](/integrations/stripe/payment-links) for configuration instructions. Screenshot 2025-03-12 at 6.20.01 PM.png * **Update Payment Status** - Invoice payment status changes from `Pending` to `SUCCEEDED` for API responses) After the payment is completed, the invoice status is updated to confirm successful payment. Once payment is confirmed and the invoice is marked as paid: * Transaction status changes from `pending` to `completed` * Credits are immediately added to the customer's wallet * Wallet balance is updated and credits become available for use Screenshot 2025-03-12 at 6.20.01 PM.png For complete transaction lifecycle details, check out [Purchased Credits (Invoiced)](/docs/wallet/transactions#purchased-credits-invoiced) in the Wallet Transactions documentation. **Payment Gateway Required** — Payment recording requires a configured payment gateway. For Example, Refer to [Stripe Connection Setup](/integrations/stripe/connection-setup) for configuration instructions. ### Credit Activation Once payment is recorded and the invoice is marked as paid: * Credits are immediately added to the customer's wallet * Transaction status updates to `completed` in wallet history * Credits become available for immediate use ### When to Use Invoice-Based Top-Ups **Financial Record Keeping** * Maintain detailed audit trails for all credit purchases * Separate wallet top-up revenue from service charges * Generate comprehensive transaction histories for accounting **Enterprise Workflows** * Support purchase order and approval processes * Enable invoice-based payment for corporate customers * Provide formal documentation for business expense tracking **Compliance and Reporting** * Meet regulatory requirements with detailed financial records * Generate reports for credit purchase analysis * Track customer acquisition and spending patterns *** ## Related Documentation * [Wallet Transactions](/docs/wallet/transactions) - Monitor and manage all wallet transactions * [Auto Top-Up Wallet](/docs/wallet/auto-top-up) - Configure automatic wallet recharging * [Low Balance Alert](/docs/wallet/low-balance-alert) - Set up balance threshold notifications *** # Wallet Transactions Source: https://docs.flexprice.io/docs/wallet/transactions Track and monitor all wallet activity including credits added, debits, and transaction status ## Overview Every action that affects a wallet balance is recorded as a transaction. The **Transactions** section provides a complete audit trail of all wallet activity, making it easy to track credits, debits, and monitor account balances. Wallet Transactions ## Transaction Types Wallet transactions fall into three main categories based on how they affect the wallet balance: ### Free Credits Added Free Credits Transaction Promotional or complimentary credits added directly to the wallet: **Characteristics:** * Credits are immediately available for use * Balance updates instantly * No payment or invoice required * Reflected with a positive amount (e.g., +100 \$) **Common Use Cases:** * Welcome bonuses for new customers * Promotional campaigns * Service compensation * Customer loyalty rewards * Trial credits **Example**: You add \$100 in free credits as a welcome bonus for a new customer. The credits are immediately available, and the customer can start using them right away. *** ### Purchased Credits (Invoiced) Free Credits Transaction Credits purchased by the customer that are linked to an invoice: **Characteristics:** * Transaction remains in **pending state** until invoice is paid * Balance is **not updated** while pending * Credits become available only after invoice payment * Reflected with a positive amount (e.g., +20 \$) **Payment Flow:** Customer purchases credits and transaction is created in pending state An invoice is automatically created for the purchased amount Transaction remains pending while waiting for invoice payment When the invoice is paid, transaction status updates to completed Wallet balance is updated and credits become available for use **Common Use Cases:** * Enterprise customers with NET payment terms * Credit packages sold with invoicing * Prepaid purchases requiring payment confirmation * Customers who prefer invoiced billing **Example**: A customer purchases 500 credits worth \$500. The transaction shows as "Purchased Credits (Invoiced)" and remains pending. Once the customer pays the invoice, the transaction completes and the credits are added to their wallet balance. **Pending State**: While a purchased credit transaction is pending, the wallet balance remains unchanged. This ensures credits are only added after payment confirmation. #### Auto-Complete Purchased Credit Transactions For automated payment flows, you can enable the `AutoCompletePurchasedCreditTransaction` setting for your account. When enabled: * Purchased credit transactions are created directly as **completed** instead of pending * Credits are added to the wallet **immediately** upon transaction creation * Invoice is still generated but marked as paid automatically * Useful for automated payment systems where payment is guaranteed **When to Enable:** * Integration with pre-authorized payment methods * Automated billing systems with payment confirmation * Trust-based credit purchases for verified enterprise customers **Note**: This setting bypasses the pending state entirely. Only enable if you have a reliable payment confirmation mechanism in place. *** ### Debited Free Credits Transaction Credits deducted from the wallet for usage, payments, or manual adjustments: **Characteristics:** * Credits are immediately removed from wallet * Balance updates instantly * Reflected with a negative amount (e.g., -30 \$) * Cannot be reversed once completed **Common Use Cases:** * Feature usage charges (API calls, storage, etc.) * Subscription fees paid from wallet * Invoice payments using wallet credits * Manual balance adjustments by admins * Service consumption **Example**: A customer uses 30 credits during the billing period for API calls. The wallet is debited by 30 credits, reducing their available balance. ## Transaction Details Each transaction record in the Transactions section includes: | Field | Description | | ---------------- | --------------------------------------------------------------------- | | **Transactions** | Type and description of the transaction | | **Payment Date** | Date when the transaction was created or completed | | **Expiry Date** | When credits expire (if applicable, otherwise shown as `--`) | | **Priority** | Processing priority for credit consumption (shown as `--` if not set) | | **Amount** | Transaction amount in currency and credits | ### Understanding Transaction Amounts Transaction amounts are displayed in two formats: * **Currency Amount**: Dollar value (e.g., +100, -30) * **Credit Amount**: Credit units (e.g., +100 credits, -30 credits) **Positive amounts** (+) indicate credits **added** to the wallet\ **Negative amounts** (-) indicate credits **removed** from the wallet ## Accessing Transaction History View complete transaction history for any customer wallet: Go to **Billing** → **Customers** → Select customer → **Wallet** tab Scroll down to the **Transactions** section below the wallet details View all transactions with type, date, amount, and status information ## Transaction States Understanding transaction states helps you monitor payment flows and credit availability: ### Completed Transactions * Credits are available for use * Balance has been updated * Transaction is finalized and cannot be changed * Applies to: Free credits, paid invoiced credits, and debits ### Pending Transactions * Credits are not yet available * Balance has not been updated * Waiting for invoice payment * Applies to: Purchased credits (invoiced) only **Monitoring Pending Transactions**: Keep an eye on pending transactions to ensure invoices are paid promptly. Pending credits won't be available until payment is received. ## Use Cases ### For Finance Teams 💡 **Audit Trail**: Complete transaction history for accounting and compliance 💡 **Reconciliation**: Match wallet transactions with invoices and payments 💡 **Revenue Tracking**: Monitor purchased credit transactions and payment flow ### For Customer Support 💡 **Quick Reference**: Review transaction history when resolving customer inquiries 💡 **Balance Investigation**: Understand why a customer's balance changed 💡 **Payment Status**: Check if purchased credits are pending or completed ### For Analytics 💡 **Usage Patterns**: Analyze customer spending and credit consumption trends 💡 **Credit Utilization**: Track how customers use free vs. purchased credits 💡 **Payment Behavior**: Monitor invoice payment timelines for purchased credits ## Related Documentation * [Top-Up Wallet](/docs/wallet/top-up) - Learn about manual wallet top-ups * [Auto Top-Up](/docs/wallet/auto-top-up) - Configure automatic wallet recharging * [Low Balance Alert](/docs/wallet/low-balance-alert) - Set up balance alerts # Webhooks Source: https://docs.flexprice.io/docs/webhook/webhooks Learn about webhook events in Flexprice Webhooks let you receive real-time notifications about events in your Flexprice account, such as invoice creation, payment status updates, subscription changes, and more. There are **two ways** webhooks can be delivered: * **Native Flexprice Webhooks**: Simple, direct POST to your endpoint. * **Svix Webhooks**: Advanced, robust delivery with retries, history, signatures, and more (powered by [Svix](https://docs.svix.com/)). **Flexprice Cloud uses Svix for webhook delivery. For self hosting, if you haven't explicitly enabled Svix, you're using native webhooks by default.** ## Webhook Event Types You can receive webhook notifications for a range of events: ### Invoice Events * `invoice.create.drafted` - Fired when a new invoice is created in draft state * `invoice.update.finalized` - Fired when an invoice is finalized and ready for payment * `invoice.update.payment` - Fired when a payment is recorded on an invoice * `invoice.update.voided` - Fired when an invoice is voided * `invoice.payment.overdue` - Fired when an invoice payment is overdue ### Subscription Events * `subscription.created` - Fired when a new subscription is created * `subscription.updated` - Fired when a subscription is updated * `subscription.paused` - Fired when a subscription is paused * `subscription.cancelled` - Fired when a subscription is cancelled * `subscription.resumed` - Fired when a paused subscription is resumed ### Feature Events * `feature.created` - Fired when a new feature is created * `feature.updated` - Fired when feature details are updated * `feature.deleted` - Fired when a feature is deleted ### Entitlement Events * `entitlement.created` - Fired when a new entitlement is created * `entitlement.updated` - Fired when entitlement details are updated * `entitlement.deleted` - Fired when an entitlement is deleted ### Wallet Events * `wallet.created` - Fired when a new wallet is created * `wallet.updated` - Fired when wallet details are updated * `wallet.terminated` - Fired when a wallet is terminated * `wallet.transaction.created` - Fired when a new wallet transaction is created ### Payment Events * `payment.created` - Fired when a new payment is created * `payment.updated` - Fired when payment details are updated * `payment.failed` - Fired when a payment attempt fails * `payment.success` - Fired when a payment is successful * `payment.pending` - Fired when a payment is awaiting processing ### Customer Events * `customer.created` - Fired when a new customer is created * `customer.updated` - Fired when customer information is updated * `customer.deleted` - Fired when a customer is deleted ### Checkout Events * `checkout.session.initiated` - Fired when a Checkout Session is created and the payment link is returned to the caller * `checkout.session.completed` - Fired when payment is confirmed; the subscription is activated and the invoice is finalized * `checkout.session.failed` - Fired when the payment fails or the provider cancels the payment link * `checkout.session.expired` - Fired when the session window elapses without a completed payment For more on how Checkout Sessions work and how to configure the payment provider webhook events that trigger these, see [Checkout Sessions](/docs/checkout/checkout-sessions). *(More event types may be added in the future.)* ## Webhook Payload Format Every webhook payload follows this structure: ```json theme={null} { "subscription": "" } ``` * All event types use this base schema * The payload field contains event-specific data ## How Webhook Delivery Works ### Native Webhook System * **Default mode.** * When a subscribed event occurs, Flexprice makes a simple HTTP POST to your provided webhook URL. * A JSON payload with event data is sent. * If your endpoint responds with a `2xx` status, the event is marked as delivered. * If your endpoint fails (non-2xx, timeout, etc.), Flexprice attempts retries with the following configuration: * Maximum retries: 3 (default) * Initial interval: 1 second * Maximum interval: 10 seconds * Multiplier: 2.0 * Maximum elapsed time: 2 minutes **Summary:** Fast, direct, and simple. Good for lightweight use-cases or internal integrations. ### Svix Webhook System * **Advanced mode** (opt-in; see [Svix docs](https://docs.svix.com/) for more on their delivery system). * When enabled, Flexprice hands off event delivery to Svix. * Svix provides: * **Robust retries:** Automatic, exponential backoff, no event lost due to transient issues * **Delivery guarantees:** At-least-once delivery, dead-letter queues for persistent failures * **Message history and logs:** Track every delivery, status, and error online * **Signature verification:** Every request is signed with HMAC-SHA256 for security * **Versioning & secret management:** Change secrets or endpoint URLs safely without missed events The retry schedule for failed deliveries is: * 5 minutes after the first failure * 30 minutes after the second failure * 2 hours after the third failure * 5 hours after the fourth failure * 10 hours after the fifth failure **Summary:** Svix is the recommended choice for production, customer-facing, or high-uptime integrations. If you want reliability, observability, and security, use Svix. ## Setting Up Webhooks To enable webhooks on flexprice: 1. Go to the **Webhooks** section in your Flexprice dashboard 2. Click the **Add Endpoint** button 3. Enter your endpoint URL (e.g., `https://play.svix.com/in/e_...`) 4. The endpoint will be created with a 0.0% error rate initially 5. You can monitor message delivery, view logs, and track activity in the dashboard Your configured webhook endpoint will be displayed in the **Endpoints** tab, where you can: * View the error rate for message delivery * Access message logs with detailed delivery information * See historical delivery attempts and statistics * Monitor webhook activity in real-time The **Event Catalog** tab shows all available event types you can subscribe to, with detailed descriptions of when each event is fired. The **Logs** tab provides a comprehensive view of message delivery attempts, including: * Event type * Message ID * Timestamp * Delivery status * Detailed error information if delivery fails The **Activity** tab shows historical delivery metrics and attempts over time. ## Security & Verification * Every payload is signed using HMAC-SHA256 (default) or Ed25519 * Verify signatures using: * Svix's official SDKs * Manual verification using `svix-signature`, `svix-id`, and `svix-timestamp` headers * Automatic timestamp validation prevents replay attacks * Optional IP address validation For detailed signature verification instructions, see the [Svix signature docs](https://docs.svix.com/receiving/verifying-payloads). ## Best Practices 1. **Use Svix for Production**: If you're building a production system, use Svix for its reliability and security features. 2. **Always Verify Signatures**: Whether using native or Svix webhooks, always verify the authenticity of incoming webhooks. 3. **Process Asynchronously**: Handle webhook processing in the background to avoid timeouts. 4. **Be Idempotent**: Design your handlers to safely process duplicate events. 5. **Use HTTPS**: Always use HTTPS endpoints for webhook delivery. 6. **Monitor Deliveries**: Regularly check webhook logs for failed deliveries. 7. **Quick Response**: Your endpoint should respond within 5 seconds to avoid timeouts. # Welcome to Flexprice Source: https://docs.flexprice.io/docs/welcome-to-flexprice Developers should focus on building, not billing ## What is Flexprice? **Flexprice** is an open-source platform for metering, billing and feature management, built by developers, for developers. It handles the heavy lifting of usage tracking, feature access, and billing logic — so you don’t have to build them from scratch. To get a quick overview of the product, watch this quick demo: Flexprice Product Demo ## Why Flexprice? Traditional billing services are often rigid. They handle basic subscriptions or payments well, but anything beyond that (credit management, feature gating, custom invoice logic) forces developers to create workarounds. This means engineering teams end up writing and maintaining tons of custom code for things like prorated subscriptions, usage limits, time zone differences and tax calculations. In short, billing becomes a developer problem when the tools don’t flex to your product’s needs, eating up valuable development time and causing endless maintenance headaches. Plug-and-play SDKs for popular languages make integration seamless. Just send usage events, and Flexprice handles aggregation, metering, and billing logic in real time. Self-host Flexprice on your own infrastructure for full transparency and control. There's no vendor lock-in or surprise fees. Seat-based, usage-based, or hybrid models, Flexprice handles it all. You can handle credit grants, tokens or any usage metric. ## Core Features Define custom events like API calls or compute time. Aggregate usage in real-time with low-latency tracking. Built to handle countless events at peak load and more. Grant prepaid or promotional credits programmatically. Set auto top-ups and expiration rules. Configure credit-based workflows without extra logic. Define seat-based, usage-based, or hybrid models. Launch custom plans and per-customer overrides. Adapt pricing over time without new code. Define features, set limits, and control access per customer. Enforce limits and gate access by usage. Enable or restrict features without manual code changes. Generate accurate invoices from usage, subscriptions, or credits. Simplify reconciliation for finance teams. Provide customers full visibility into charges. ## Our SDKs # Chargebee Connection Setup Source: https://docs.flexprice.io/integrations/chargebee/connection-setup Complete guide to setting up and configuring Chargebee connections in Flexprice ## Overview A Chargebee connection in Flexprice stores encrypted credentials that allow the system to interact with your Chargebee account for: * Creating item families, items, and item prices in Chargebee * Synchronizing customers to Chargebee * Syncing invoices to Chargebee for payment collection * Receiving webhook notifications from Chargebee ## Prerequisites Before setting up your Chargebee connection, ensure you have: 1. **Active Chargebee Account** - Sign up at [chargebee.com](https://chargebee.com) 2. **API Key** - Available in your Chargebee Dashboard 3. **Webhook Endpoint** - Configured in Chargebee Dashboard 4. **Flexprice Environment** - Valid tenant and environment IDs ## Step 1: Gather Chargebee Credentials ### Required Credentials | Credential | Location in Chargebee Dashboard | Required | Purpose | | -------------------- | ------------------------------------ | -------- | ------------------------------ | | **Site Name** | Settings → API & Webhooks → Site | ✅ | Chargebee site identifier | | **API Key** | Settings → API & Webhooks → API Keys | ✅ | API authentication | | **Webhook Username** | Settings → API & Webhooks → Webhooks | ❌ | Basic Auth username (optional) | | **Webhook Password** | Settings → API & Webhooks → Webhooks | ❌ | Basic Auth password (optional) | ### Finding Your Credentials 1. **Site Name:** * Go to Chargebee Dashboard → Settings → API & Webhooks * Your site name is displayed at the top (e.g., `acme-test`) 2. **API Key:** * Go to Chargebee Dashboard → Settings → API & Webhooks → API Keys * Copy your API key (starts with your site name) * Use test API keys for development 3. **Webhook Credentials (Optional):** * Go to Chargebee Dashboard → Settings → API & Webhooks → Webhooks * Configure Basic Authentication for webhook security * Copy the username and password ## Step 2: Configure Webhook Endpoint in Chargebee ### Webhook URL Format ``` https://api.cloud.flexprice.io/v1/webhooks/chargebee/tenant_01K1TJDVNSN7TWY8CZY870QMNV/env_01K1TJJF0CJR410C6QVPYQTNV0 ``` ### Required Webhook Events Configure these events in your Chargebee webhook endpoint: | Event Type | Purpose | | ------------------- | ------------------------------------ | | `payment_succeeded` | Track successful payment completions | ### Setting Up the Webhook 1. Go to Chargebee Dashboard → Settings → API & Webhooks → Webhooks 2. Click **+ Add New Webhook** 3. Enter your webhook URL 4. Select the required events listed above 5. Configure Basic Authentication (optional but recommended) 6. Click **Save** ## Step 3: Create Chargebee Connection ### Using Flexprice Dashboard You can create a Chargebee connection directly from the Flexprice dashboard. Connect to Chargebee ### API Request **Endpoint:** `POST /api/v1/connections` **Headers:** ```http theme={null} Content-Type: application/json Authorization: Bearer your_api_key X-Tenant-ID: your_tenant_id X-Environment-ID: your_environment_id ``` **Request Body:** ```json theme={null} { "name": "Chargebee Production", "provider_type": "chargebee", "encrypted_secret_data": { "site": "acme-production", "api_key": "acme-production_api_key_...", "webhook_username": "webhook_user", "webhook_password": "webhook_password" } } ``` ### Response ```json theme={null} { "id": "conn_1234567890abcdef", "name": "Chargebee Production", "provider_type": "chargebee", "status": "active", "created_at": "2024-01-20T10:30:00Z", "updated_at": "2024-01-20T10:30:00Z" } ``` ### Editing an Existing Connection You can edit an existing Chargebee connection to update credentials, sync configuration, or webhook settings. Edit Chargebee Connection **Editable Fields:** * **Connection Name**: Update the friendly name for the connection * **API Key**: Update Chargebee API key if rotated * **Site**: Update Chargebee site name * **Sync Configuration**: Enable or disable invoice sync * **Webhook Configuration**: Update webhook username and password ## Security Best Practices ### Credential Management 1. **Environment Separation**: Use different API keys for test/production 2. **Key Rotation**: Regularly rotate your Chargebee API keys 3. **Access Control**: Limit API key permissions in Chargebee 4. **Encryption**: All credentials are encrypted at rest in Flexprice ### Webhook Security 1. **HTTPS Only**: Always use HTTPS for webhook endpoints 2. **Basic Authentication**: Use Basic Auth for webhook verification (recommended) 3. **Secret Management**: Keep webhook credentials secure and rotate regularly 4. **Rate Limiting**: Implement rate limiting on webhook endpoints ## Troubleshooting ### Common Issues | Issue | Cause | Solution | | --------------------- | ---------------------------- | ----------------------------------------------- | | Connection test fails | Invalid API key or site name | Verify credentials in Chargebee Dashboard | | Webhook not received | Incorrect webhook URL | Check URL format and endpoint | | Authentication fails | Wrong webhook credentials | Update webhook username/password in connection | | API calls fail | Invalid site name | Verify site name matches your Chargebee account | ### Debug Steps 1. **Check Chargebee Dashboard**: Verify API key and site name 2. **Test API Key**: Use Chargebee API to test key validity 3. **Monitor Webhooks**: Check webhook delivery logs in Chargebee 4. **Review Logs**: Check Flexprice application logs for errors ## Next Steps After setting up your Chargebee connection: 1. **Create Item Family**: Create an item family in Chargebee (required for plan sync) 2. **Sync Plans**: Sync your Flexprice plans to Chargebee 3. **Test Invoice Sync**: Create a test invoice and sync to Chargebee 4. **Monitor Webhooks**: Ensure webhook events are being received 5. **Go Live**: Switch to production credentials when ready # Chargebee Customer Synchronization Source: https://docs.flexprice.io/integrations/chargebee/customer-sync Complete guide to customer synchronization between Flexprice and Chargebee ## Overview Customer synchronization in Flexprice ensures that customers exist in Chargebee before invoice sync. Unlike other integrations, **customers are only synced when an invoice is being synced to Chargebee**, not during payment link creation or other operations. ### Key Concepts * **On-Demand Sync**: Customers are synced only when invoice sync is triggered * **Invoice-Triggered**: Sync happens automatically during invoice sync * **Entity Integration Mapping**: Links Flexprice customers to Chargebee customer IDs * **Metadata Storage**: Customer metadata stores cross-platform references * **Duplicate Prevention**: Intelligent checks prevent duplicate customer creation ## Synchronization Flow Customer sync is **invoice-triggered** and happens automatically: ```mermaid theme={null} graph LR A[Invoice Sync
Request] --> B[Ensure Customer
Synced to Chargebee] B --> C{Check
Metadata} C -->|Has ID| D[Return Existing
Customer] C -->|No ID| E[Check Integration
Mapping] E --> F{Mapping
Exists?} F -->|Yes| G[Update Customer
Metadata] F -->|No| H[Create Customer
in Chargebee] G --> I[Return Customer
Info] H --> J[Update Metadata
& Mapping] J --> I I --> K[Continue Invoice
Sync] ``` ### When Customer Sync Happens Customer sync is triggered **only** when: * An invoice is being synced to Chargebee * Invoice outbound sync is enabled in the Chargebee connection * Invoice status changes from `DRAFT` to `FINALIZED` **Customer sync does NOT happen for:** * Payment link creation * Manual customer operations * Other payment flows ## Customer Data Model ### Flexprice Customer Structure ```json theme={null} { "id": "cust_1234567890abcdef", "external_id": "customer_external_123", "name": "John Doe", "email": "john@example.com", "address_line1": "123 Main St", "address_line2": "Apt 4B", "address_city": "New York", "address_state": "NY", "address_postal_code": "10001", "address_country": "US", "metadata": { "chargebee_customer_id": "cust_chargebee123abc" }, "environment_id": "prod", "tenant_id": "tenant_123", "status": "active", "created_at": "2024-01-20T10:30:00Z" } ``` ### Customer Creation in Chargebee When creating a customer in Chargebee: **Required Fields:** * `email`: Customer email address * `auto_collection`: Set to `"on"` (enables automatic payment collection) **Optional Fields:** * `first_name`: Customer name (from Flexprice customer name) * `billing_address`: Address information if available **Address Mapping:** * `line1` → `billing_address.line1` * `line2` → `billing_address.line2` * `city` → `billing_address.city` * `state` → `billing_address.state` * `postal_code` → `billing_address.zip` * `country` → `billing_address.country` ## Customer Linking ### Metadata Storage Customer linking is maintained through metadata fields: **In Flexprice Customer:** ```json theme={null} { "metadata": { "chargebee_customer_id": "cust_chargebee123abc", "chargebee_sync_timestamp": "2024-01-20T10:30:00Z" } } ``` ### Entity Integration Mapping Customer links are also stored in the entity integration mapping table: * **Entity Type**: `customer` * **Entity ID**: Flexprice customer ID * **Provider Type**: `chargebee` * **Provider Entity ID**: Chargebee customer ID This provides a reliable lookup mechanism even if metadata is missing. ## Duplicate Prevention ### Email-Based Deduplication The system prevents duplicate customers using email addresses: 1. **Check Existing**: Look for customers with same email in Chargebee 2. **Reuse Existing**: If found, use existing Chargebee customer 3. **Link Creation**: Create cross-platform links 4. **Audit Trail**: Log all sync operations ### External ID Matching For customers with external IDs: 1. **Priority Matching**: External ID takes precedence over email 2. **Fallback to Email**: If no external ID match, use email 3. **Manual Resolution**: Conflicts require manual intervention ## Auto Collection ### Automatic Payment Collection When creating customers in Chargebee, `auto_collection` is set to `"on"`: **Benefits:** * Chargebee automatically attempts to collect payment when invoices are due * Reduces manual payment processing * Improves cash flow **Note**: This setting is required for Chargebee to process payments automatically. ## Error Handling ### Common Sync Errors | Error | Cause | Resolution | | ---------------------- | ---------------------------- | ----------------------------------- | | "Customer not found" | Invalid customer ID | Verify customer exists in Flexprice | | "Email already exists" | Duplicate email in Chargebee | System will reuse existing customer | | "Invalid email format" | Malformed email address | Fix email format in Flexprice | | "Chargebee API error" | Chargebee connection issue | Check Chargebee credentials | ### Sync Failure Recovery When customer sync fails: 1. **Log Error**: Record failure with details 2. **Invoice Sync Fails**: Invoice sync cannot proceed without customer 3. **Manual Intervention**: Admin can retry customer sync 4. **Notification**: Alert on persistent failures ## Testing Customer Sync ### Test Environment Setup 1. **Test Chargebee Account**: Use Chargebee test mode 2. **Test Customers**: Create test customers in Flexprice only 3. **Test Invoice Sync**: Trigger invoice sync to test customer sync ### Testing Flow 1. **Create Test Customer**: Add customer in Flexprice (no sync yet) 2. **Verify No Sync**: Customer should NOT exist in Chargebee initially 3. **Create Invoice**: Create and finalize a test invoice 4. **Sync Invoice**: Trigger invoice sync to Chargebee 5. **Verify Auto-Sync**: Customer should now exist in Chargebee 6. **Verify Reuse**: Sync another invoice - should use existing customer ## Next Steps After understanding customer sync: 1. **Create Customers**: Create customers in Flexprice as normal 2. **Create Invoices**: Create and finalize invoices 3. **Sync Invoices**: Invoice sync will automatically handle customer sync 4. **Verify Sync**: Check customers in Chargebee dashboard For detailed information about invoice sync, see [Invoice Sync Documentation](/integrations/chargebee/invoice-sync). # Chargebee Invoice Sync & Webhook Handling Source: https://docs.flexprice.io/integrations/chargebee/invoice-sync Complete guide to syncing invoices to Chargebee and handling webhooks ## Overview Invoice sync in Flexprice sends invoices to Chargebee for payment processing. The sync process handles different pricing models (flat fee, per unit, tiered) with precision-aware amount handling to ensure accurate billing. ### Key Features * **Automatic Sync**: Invoices sync automatically when finalized * **Precision Handling**: Different strategies for flat fee vs tiered pricing * **Customer Auto-Sync**: Customers are synced automatically during invoice sync * **Webhook Processing**: Payment webhooks update invoice status automatically * **Amount Accuracy**: Ensures exact amount matching with Flexprice calculations ## Invoice Sync Flow ```mermaid theme={null} graph TD A[Invoice Finalized] --> B[Check Chargebee
Connection] B --> C{Connection
Enabled?} C -->|No| D[Skip Sync] C -->|Yes| E[Check Invoice
Sync Enabled] E --> F{Invoice Sync
Enabled?} F -->|No| D F -->|Yes| G[Check Existing
Mapping] G --> H{Already
Synced?} H -->|Yes| I[Return Existing
Invoice] H -->|No| J[Ensure Customer
Synced] J --> K[Build Line Items] K --> L[Create Invoice
in Chargebee] L --> M[Save Entity
Mapping] M --> N[Sync Complete] ``` ## Invoice Sync Timing Invoices are automatically synced to Chargebee when: 1. **Invoice Finalization**: Invoice status changes from `DRAFT` to `FINALIZED` 2. **Invoice Sync Enabled**: Invoice outbound sync is enabled in Chargebee connection 3. **Connection Active**: Chargebee connection exists and is active **Manual Sync:** * Invoices can also be synced manually via API if needed ## Line Item Handling ### Critical: Different Strategies by Pricing Model Flexprice uses **different strategies** for sending line items to Chargebee based on the pricing model. This is crucial for maintaining precision and accuracy. ### Flat Fee / Per Unit / Package Pricing For **non-tiered** pricing models (flat\_fee, per\_unit, package): **Strategy:** Send `quantity=1` + exact `unit_amount` ```json theme={null} { "item_price_id": "price_123", "quantity": 1, "unit_amount": 1000 // Exact amount in smallest currency unit (cents) } ``` **Why This Approach:** * Chargebee has **2 decimal precision** limitations * Sending exact amount as `unit_amount` ensures **perfect accuracy** * Quantity=1 means "charge this exact amount once" * Prevents rounding errors from quantity × unit\_price calculations **Example:** * Flexprice amount: \$10.50 * Sent to Chargebee: `quantity=1, unit_amount=1050` (cents) * Result: Exact \$10.50 charge ### Tiered Pricing For **tiered** pricing models (tiered, volume, stairstep): **Strategy:** Send **only quantity**, let Chargebee calculate amount ```json theme={null} { "item_price_id": "price_123", "quantity": 1500 // NO unit_amount - Chargebee calculates based on tiers } ``` **Why This Approach:** * Tier calculations involve complex multi-tier math * Chargebee's tier calculation may differ slightly due to rounding * Sending quantity allows Chargebee to apply its tier logic correctly * Prevents precision mismatches from manual tier calculations **Important Notes:** * **DO NOT** set `unit_amount` for tiered pricing (Chargebee will reject it) * Chargebee calculates the amount based on tier definitions * There may be **slight differences** (usually \< \$0.01) due to rounding * This is expected and acceptable for tiered pricing **Example:** * Flexprice quantity: 1500 units * Flexprice calculated amount: \$125.75 * Sent to Chargebee: `quantity=1500` (no unit\_amount) * Chargebee calculates: \$125.74 (may differ slightly) * Difference: \$0.01 (acceptable rounding difference) ### Pricing Model Detection The system automatically detects the pricing model: 1. **Lookup Item Price**: Retrieves Chargebee item price by Flexprice price ID 2. **Check Pricing Model**: Reads `pricing_model` from Chargebee item price 3. **Determine Strategy**: * If `tiered`, `volume`, or `stairstep` → Send quantity only * Otherwise → Send quantity=1 + unit\_amount ## Amount Conversion ### Currency Precision All amounts are converted to smallest currency unit: | Currency | Precision | Smallest Unit | Example | | -------- | --------- | ------------- | -------------------- | | USD | 2 | Cents | \$10.50 → 1050 cents | | EUR | 2 | Cents | €10.50 → 1050 cents | | INR | 2 | Paise | ₹10.50 → 1050 paise | | JPY | 0 | Yen | ¥100 → 100 yen | **Conversion Formula:** ``` amount_in_smallest_unit = round(amount × 10^precision) ``` ### Why 2 Decimal Precision Matters Chargebee stores all monetary values as integers in the smallest currency unit. This means: * **2 decimal currencies** (USD, EUR, INR): Stored as cents/paise * **0 decimal currencies** (JPY): Stored as whole units **Precision Issues:** * If we send `quantity × unit_price` for flat fees, rounding errors can occur * Example: quantity=1, unit\_price=10.505 → rounds to 10.51 (wrong!) * Solution: Send exact amount as `unit_amount` with `quantity=1` ## Invoice Creation ### Request Structure ```json theme={null} { "customer_id": "cust_chargebee123", "auto_collection": "on", "line_items": [ { "item_price_id": "price_123", "quantity": 1, "unit_amount": 1000 } ], "invoice_date": 1642680600, "due_date": 1642767000 } ``` ### Line Item Fields | Field | Required | Description | | --------------- | -------- | ----------------------------------------------------------- | | `item_price_id` | ✅ | Chargebee item price ID (from entity mapping) | | `quantity` | ✅ | Quantity (1 for flat fees, actual quantity for tiers) | | `unit_amount` | ❌ | Unit amount in smallest currency unit (only for non-tiered) | | `date_from` | ❌ | Period start date (Unix timestamp) | | `date_to` | ❌ | Period end date (Unix timestamp) | | `description` | ❌ | Line item description | ### Auto Collection Invoices are created with `auto_collection: "on"`: * Chargebee automatically attempts payment collection * Uses customer's default payment method * Sends payment reminders if needed ## Entity Mapping ### Invoice Mapping Storage After syncing an invoice, Flexprice stores the mapping: * **Entity Type**: `invoice` * **Entity ID**: Flexprice invoice ID * **Provider Type**: `chargebee` * **Provider Entity ID**: Chargebee invoice ID This mapping is used for: * Webhook processing (finding Flexprice invoice from Chargebee invoice) * Duplicate prevention (checking if invoice already synced) * Status updates (updating Flexprice invoice from Chargebee) ## Webhook Handling ### Supported Webhooks | Event Type | Purpose | | ------------------- | ------------------------------------ | | `payment_succeeded` | Track successful payment completions | ### Webhook URL Format ``` https://api.cloud.flexprice.io/v1/webhooks/chargebee/{tenant_id}/{environment_id} ``` ### Webhook Authentication Chargebee webhooks support Basic Authentication: 1. **Configure in Chargebee**: Set username and password in webhook settings 2. **Store in Connection**: Save credentials in Chargebee connection 3. **Verify on Receipt**: Flexprice verifies Basic Auth credentials 4. **Optional**: If not configured, webhooks are still accepted (less secure) ### Payment Succeeded Webhook When a payment succeeds in Chargebee: **Webhook Payload:** ```json theme={null} { "event_type": "payment_succeeded", "content": { "transaction": { "id": "txn_123", "amount": 1000, "currency_code": "USD", "payment_method": "card" }, "invoice": { "id": "inv_chargebee123" } } } ``` **Processing Flow:** 1. **Receive Webhook**: Flexprice receives the webhook 2. **Verify Auth**: Verify Basic Authentication (if configured) 3. **Find Invoice**: Look up Flexprice invoice ID from Chargebee invoice ID 4. **Check Duplicate**: Verify payment doesn't already exist 5. **Create Payment**: Create payment record in Flexprice 6. **Update Invoice**: Update invoice payment status and amounts 7. **Reconcile**: Reconcile invoice with payment ### Payment Record Creation When processing a payment webhook: **Payment Record:** ```json theme={null} { "id": "pay_123", "destination_type": "INVOICE", "destination_id": "inv_flexprice123", "amount": "10.00", "currency": "USD", "payment_status": "SUCCEEDED", "payment_gateway": "chargebee", "gateway_payment_id": "txn_123", "succeeded_at": "2024-01-20T10:30:00Z" } ``` **Idempotency:** * Payments are checked for duplicates using `gateway_payment_id` * Prevents duplicate payment records from webhook retries ## Error Handling ### Common Sync Errors | Error | Cause | Resolution | | ------------------------ | ----------------------------- | ----------------------------------- | | "Customer not found" | Customer not synced | Customer sync happens automatically | | "Item price not found" | Price not synced to Chargebee | Sync plan to Chargebee first | | "Invalid amount" | Amount precision issue | Check amount conversion | | "Invoice already synced" | Duplicate sync attempt | Use existing Chargebee invoice | ### Webhook Errors | Error | Cause | Resolution | | ------------------------ | ---------------------------- | ------------------------------------- | | "Authentication failed" | Wrong Basic Auth credentials | Update webhook credentials | | "Invoice not found" | Invoice mapping missing | Check entity integration mapping | | "Payment already exists" | Duplicate webhook | Idempotency check prevents duplicates | ## Best Practices ### Invoice Sync 1. **Sync Plans First**: Always sync plans to Chargebee before creating invoices 2. **Verify Items**: Ensure item prices exist in Chargebee 3. **Check Amounts**: Verify amounts match between Flexprice and Chargebee ### Amount Precision 1. **Flat Fees**: Always use quantity=1 + exact unit\_amount 2. **Tiered Pricing**: Accept small rounding differences (\< \$0.01) 3. **Currency Awareness**: Be aware of currency precision (2 vs 0 decimals) 4. **Testing**: Test with various amounts to verify precision ## Testing ### Test Invoice Sync 1. **Create Test Invoice**: Create invoice in Flexprice 2. **Finalize Invoice**: Change status to FINALIZED 3. **Verify Sync**: Check Chargebee dashboard for invoice 4. **Verify Amounts**: Compare amounts between Flexprice and Chargebee 5. **Test Payment**: Process test payment in Chargebee 6. **Verify Webhook**: Check payment record in Flexprice ## Next Steps After setting up invoice sync: 1. **Sync Plans**: Ensure all plans are synced to Chargebee 2. **Create Invoices**: Create and finalize invoices 3. **Monitor Sync**: Check sync status and logs 4. **Test Payments**: Process test payments and verify webhooks 5. **Go Live**: Switch to production when ready For detailed information about item sync, see [Item Sync Documentation](/integrations/chargebee/item-sync). # Chargebee Item Sync Source: https://docs.flexprice.io/integrations/chargebee/item-sync Complete guide to syncing item families, items, and item prices to Chargebee ## Overview Item sync in Flexprice creates the necessary Chargebee infrastructure (item families, items, and item prices) to support invoice synchronization. This enables Flexprice plans and prices to be represented in Chargebee's item-based pricing model. ### Key Concepts * **Item Family**: A container for related items in Chargebee (required before syncing plans) * **Item**: Represents a chargeable entity (one per price/currency combination) * **Item Price**: Defines the pricing model and rates for an item * **One Item Per Currency**: Chargebee only allows one item price per currency per item, so we create separate items for each price ## Item Family ### Overview An item family is a container that groups related items together in Chargebee. You must create at least one item family in Chargebee before syncing plans. ### Creating an Item Family **In Chargebee Dashboard:** 1. Go to Products → Item Families 2. Click **+ Add Item Family** 3. Enter a name (e.g., "Flexprice Charges") 4. Click **Save** **Via API:** ```json theme={null} { "id": "item_family_123", "name": "Flexprice Charges" } ``` ### Item Family Selection When syncing plans, Flexprice automatically: 1. Retrieves all item families from Chargebee 2. Selects the **latest** item family (most recently created) 3. Uses it for all synced items **Note**: Ensure you have at least one item family in Chargebee before syncing plans. ## Item Sync Process ### Plan Sync Flow When you sync a Flexprice plan to Chargebee: ```mermaid theme={null} graph TD A[Sync Plan Request] --> B[Get Latest
Item Family] B --> C{Item Family
Exists?} C -->|No| D[Error: Create Item
Family First] C -->|Yes| E[For Each Price
in Plan] E --> F[Create Item] F --> G[Create Item Price] G --> H[Save Entity
Mapping] H --> I{More Prices?} I -->|Yes| E I -->|No| J[Sync Complete] ``` ### Item Creation For each price in a plan, Flexprice creates: 1. **One Item** (type: `charge`) * ID format: `charge_{uuid}` * Name: Same as ID (must be unique) * Type: `charge` (for one-time/recurring charges) * Item Family: Latest item family from Chargebee * External Name: `{display_name} - {currency}` (e.g., "API Calls - USD") 2. **One Item Price** (linked to the item) * ID: Flexprice price ID (for easy mapping) * Item ID: The created item ID * External Name: `{display_name} - {currency}` * Currency: Price currency * Pricing Model: Mapped from Flexprice billing model ### Why Separate Items Per Price? Chargebee has a limitation: **only one item price per currency per item**. To support multiple currencies or multiple prices per plan, we create separate items for each price. **Example:** * Plan: "Pro Plan" with prices in USD and EUR * Result: 2 items created (`charge_uuid1` for USD, `charge_uuid2` for EUR) ## Item Price Sync ### Pricing Model Mapping Flexprice billing models are mapped to Chargebee pricing models: | Flexprice Billing Model | Flexprice Type | Chargebee Pricing Model | | ----------------------- | -------------- | ----------------------- | | `FLAT_FEE` | `FIXED` | `flat_fee` | | `FLAT_FEE` | `USAGE` | `per_unit` | | `PACKAGE` | - | `package` | | `TIERED` | `VOLUME` | `volume` | | `TIERED` | `SLAB` | `tiered` | ### Flat Fee / Per Unit Pricing For flat fee and per unit pricing: ```json theme={null} { "id": "price_123", "item_id": "charge_uuid1", "pricing_model": "flat_fee", "price": 1000, // Amount in smallest currency unit (cents for USD) "currency_code": "USD" } ``` **Details:** * `price`: Amount converted to smallest currency unit (cents for USD, paise for INR) * No tiers required * Simple fixed pricing ### Package Pricing For package pricing: ```json theme={null} { "id": "price_123", "item_id": "charge_uuid1", "pricing_model": "package", "price": 1000, "currency_code": "USD", "period": 10, // Optional: package size "period_unit": "month" } ``` **Details:** * `price`: Package price in smallest currency unit * `period`: Optional package size (e.g., 10 units per package) * Used for bulk pricing models ### Tiered Pricing For tiered/volume pricing: ```json theme={null} { "id": "price_123", "item_id": "charge_uuid1", "pricing_model": "volume", "currency_code": "USD", "tiers": [ { "starting_unit": 1, "ending_unit": 1000, "price": 100 // Price per unit in this tier (in cents) }, { "starting_unit": 1001, "ending_unit": 10000, "price": 80 }, { "starting_unit": 10001, "ending_unit": null, // Last tier has no ending "price": 60 } ] } ``` **Details:** * `tiers`: Array of tier definitions * `starting_unit`: Minimum quantity for this tier (1-based) * `ending_unit`: Maximum quantity for this tier (null for last tier) * `price`: Price per unit in this tier (in smallest currency unit) * Tiers are converted from Flexprice tier format to Chargebee format ### Tier Conversion Flexprice tiers are converted to Chargebee format: **Flexprice Tier:** ```json theme={null} { "up_to": 1000, "unit_amount": 1.00 } ``` **Chargebee Tier:** ```json theme={null} { "starting_unit": 1, "ending_unit": 1000, "price": 100 // 1.00 USD = 100 cents } ``` **Conversion Rules:** 1. `unit_amount` converted to smallest currency unit (e.g., \$1.00 → 100 cents) 2. `up_to` becomes `ending_unit` 3. `starting_unit` calculated from previous tier's `ending_unit + 1` 4. First tier always starts at 1 5. Last tier has `ending_unit: null` ## Amount Conversion ### Currency Precision All amounts are converted to the smallest currency unit based on currency precision: | Currency | Precision | Smallest Unit | Example | | -------- | --------- | ------------- | ------------------ | | USD | 2 | Cents | \$1.00 → 100 cents | | EUR | 2 | Cents | €1.00 → 100 cents | | INR | 2 | Paise | ₹1.00 → 100 paise | | JPY | 0 | Yen | ¥100 → 100 yen | **Formula:** ``` amount_in_smallest_unit = amount × 10^precision ``` ### Why Convert to Smallest Unit? Chargebee stores all prices as integers in the smallest currency unit to avoid floating-point precision issues. This ensures accurate calculations and prevents rounding errors. ## Entity Mapping ### Mapping Storage After syncing items and item prices, Flexprice stores entity mappings: **Price → Item Price Mapping:** * Entity Type: `item_price` * Entity ID: Flexprice price ID * Provider Entity ID: Chargebee item price ID * Provider Type: `chargebee` This mapping is used during invoice sync to find the correct Chargebee item price for each Flexprice price. ## Display Names ### External Names Items and item prices use external names for better invoice display: **Format:** `{display_name} - {currency}` **Examples:** * "API Calls - USD" * "Pro Plan - EUR" * "Storage - INR" **Display Name Priority:** 1. Feature name (if price has a meter/feature) 2. Meter name (if feature not found) 3. Plan name (fallback) ## Error Handling ### Common Sync Errors | Error | Cause | Solution | | ------------------------ | --------------------------- | --------------------------------------------- | | "Item family not found" | No item family in Chargebee | Create an item family first | | "Item already exists" | Duplicate item ID | Item IDs are auto-generated, should not occur | | "Invalid pricing model" | Unsupported billing model | Check billing model mapping | | "Currency not supported" | Invalid currency code | Verify currency is supported by Chargebee | ### Sync Failure Recovery When item sync fails: 1. **Partial Sync**: Some items may be created even if others fail 2. **Retry**: Re-sync the plan to create missing items 3. **Manual Cleanup**: Remove failed items from Chargebee if needed 4. **Logs**: Check application logs for detailed error messages ## Best Practices ### Item Family Management 1. **Single Family**: Use one item family for all Flexprice items (simplifies management) 2. **Naming**: Use descriptive names (e.g., "Flexprice Charges") 3. **Organization**: Group related items in the same family ### Price Sync Strategy 1. **Sync Before Invoicing**: Always sync plans before creating invoices 2. **Currency Separation**: Each currency requires a separate item 3. **Tier Precision**: Be aware that tier calculations may have slight differences due to rounding ### Testing 1. **Test Environment**: Use Chargebee test mode for development 2. **Verify Items**: Check created items in Chargebee dashboard 3. **Test Invoices**: Create test invoices to verify item prices work correctly ## Next Steps After syncing items: 1. **Verify Items**: Check created items in Chargebee dashboard 2. **Test Invoice Sync**: Create a test invoice and sync to Chargebee 3. **Monitor Mappings**: Ensure entity mappings are created correctly 4. **Go Live**: Switch to production when ready For detailed information about invoice sync, see [Invoice Sync Documentation](/integrations/chargebee/invoice-sync). # Introduction Source: https://docs.flexprice.io/integrations/integrations Connect Flexprice with your favorite tools and platforms to streamline your pricing and revenue operations ## Overview Flexprice integrations allow you to seamlessly connect with third-party platforms and services to automate your pricing workflows, synchronize customer data, and optimize revenue operations. Our robust integration ecosystem ensures that Flexprice works harmoniously with your existing technology stack. ## Why Use Integrations? Automate pricing updates, customer synchronization, and billing processes across platforms. Keep customer data, pricing, and billing information synchronized in real-time. Reduce manual work and eliminate data silos between your business tools. Get comprehensive insights by combining data from multiple platforms. ## Available Integrations Connect with Stripe for payment processing, customer sync, and billing workflows. Connect with Razorpay for payment processing, customer sync, and billing workflows. Connect with Chargebee for payment processing, item sync, customer sync, and invoice management. Connect with QuickBooks for item sync, customer sync, invoice management, and payment reconciliation. Connect with Nomod to generate payment links for invoices, push invoices to Nomod for collection, and receive payment events via Zapier. Connect with Moyasar to generate invoices, push invoices along with payment sync to Moyasar for collection. Connect with Paddle for payment processing, customer sync, invoice sync, and Flexprice Checkout (Paddle-hosted overlay). Connect with Zoho Books for OAuth, invoice sync to Zoho, webhooks for paid and voided invoices, and optional inbound customer sync from contacts. # How to Set Up Charge Automatically Source: https://docs.flexprice.io/integrations/moyasar/autopay Save a customer card via Flexprice checkout and charge invoices automatically Autopay lets Flexprice charge a customer's saved card whenever a new invoice is due, without any action from the customer. The card is captured once through the Flexprice checkout page, stored as a payment method, and reused for all subsequent invoice payments. Autopay requires an active Moyasar connection with a valid Secret Key, Publishable Key, and Webhook Secret. See [Moyasar Connection Setup](/integrations/moyasar/connection-setup) before proceeding. ## How it Works Flexprice creates a payment record to track the card save attempt and returns a secure checkout URL. The customer is redirected to the Flexprice checkout page and enters their card number, expiry, and CVC. Flexprice runs a 1 SAR verification charge to confirm the card is valid. Once confirmed, the charge is voided immediately. If the void fails, Flexprice issues a refund automatically. The customer is not charged. Flexprice receives confirmation, extracts the reusable card token, and saves it as an active payment method linked to the customer. When a new invoice is generated for the customer, Flexprice charges the saved card directly. No redirect or manual payment step is required. *** ## Setting Up Autopay for a Customer ### Using the Flexprice Dashboard Go to **Billing** → **Customers** and select the customer. In the **Integrations** section, locate the Moyasar entry and click **Setup Autopay**. Setup Autopay button on the customer page Flexprice opens the checkout page in the current tab. The customer sees a card entry form. ### Using the API ```bash theme={null} POST /v1/payments/customers/{customer_id}/setup/intent ``` **Request body:** ```json theme={null} { "provider": "moyasar", "success_url": "https://yourapp.com/customers/cust_01KVZ9HE6XC2AH7CXE6WK8SXK8" } ``` | Field | Type | Required | Description | | ------------- | ------ | -------- | ---------------------------------------------------------------------------------------------------------- | | `provider` | string | Yes | Must be `moyasar` | | `success_url` | string | No | URL the customer is redirected to after the card is saved. Must be on the same origin as your application. | **Response:** ```json theme={null} { "status": "pending_card_entry", "customer_id": "cust_01KVZ9HE6XC2AH7CXE6WK8SXK8", "checkout_url": "https://admin.flexprice.io/checkout?provider=moyasar&token=eyJhbGci..." } ``` Redirect the customer to `checkout_url`. The JWT embedded in the URL expires after 2 hours. *** ## Card Entry The customer is taken to the Flexprice checkout page where they enter their card details. Flexprice card entry form If the card issuer requires 3DS authentication, the customer is redirected to their bank's verification page and returned automatically after completing it. 3DS verification screen *** ## Payment Tracking Every autopay attempt is tracked as a payment record in Flexprice from the moment the setup intent is created. Each invoice charge attempt also creates its own record, so you can see exactly which invoices were collected automatically. ### Payment Status | Status | When it occurs | | ----------- | ---------------------------------------------------------------------------- | | `INITIATED` | Setup intent is created. Waiting for the customer to complete the card form. | | `SUCCEEDED` | Card confirmed. Token saved. | | `VOIDED` | The 1 SAR verification charge was voided successfully. | | `REFUNDED` | The 1 SAR charge could not be voided and was refunded instead. | | `FAILED` | Card authentication or the verification charge failed. | *** ## Automatic Invoice Charging Once a customer has a saved payment method, Flexprice charges it automatically when a new invoice is generated. A new invoice is created for the customer. Flexprice looks for the customer's default saved payment method. If no default is set, the most recently added active payment method is used. If a valid saved method is found, Flexprice creates a charge request using the stored token. The customer does not need to take any action. If no active saved payment method exists, Flexprice falls back to the invoice-link flow. See [Moyasar Invoice Synchronization](/integrations/moyasar/invoice-sync). When the charge is confirmed, Flexprice marks the invoice as `PAID`. *** ## Troubleshooting | Issue | Likely Cause | Resolution | | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | | Customer completed the card form but invoices are still not being charged automatically | The card save did not complete successfully | Run the setup autopay flow again for the customer | | A 1 SAR charge appeared on the customer's card but was not returned | The reversal is processed automatically and typically settles within 1-5 business days depending on the card issuer | No action needed. If it does not settle, contact Moyasar support with the payment reference | | Autopay is charging the wrong card | The customer has more than one saved card | Make sure the correct card is set as the default payment method | | A card was saved but automatic charging has stopped working | The Moyasar connection credentials may have changed or the connection is inactive | Go to **Integrations** → **Moyasar** and verify the connection is active. Re-enter credentials if needed | | The checkout URL expired before the customer completed the form | The link is valid for 2 hours from the time it was created | Run the setup autopay flow again to generate a new link | # Moyasar Connection Setup Source: https://docs.flexprice.io/integrations/moyasar/connection-setup Complete guide to setting up and configuring Moyasar connections in Flexprice ## Overview A Moyasar connection in Flexprice stores encrypted credentials so Flexprice can sync invoices to Moyasar, generate payment links, and enable autopay via the Flexprice checkout page. ### Steps required Get your Secret Key, Publishable Key, and Webhook Secret from the Moyasar Dashboard. Add the webhook URL in Moyasar, select `payment_paid`, and copy the Webhook Secret. In the Flexprice dashboard (Integrations → Moyasar → Add a connection), enter your Secret Key, Publishable Key, and Webhook Secret. ## Gather Moyasar Credentials ### Required Credentials | Credential | Location in Moyasar Dashboard | Required | Purpose | | ------------------- | -------------------------------------- | -------- | ------------------------------------------------------------- | | **Secret Key** | Settings → API Keys | ✅ | API authentication for all server-side operations | | **Publishable Key** | Settings → API Keys | ✅ | Required for the Flexprice checkout page (autopay card entry) | | **Webhook Secret** | Settings → Webhooks → \[Your Endpoint] | ✅ | Webhook signature verification | ### Finding Your Credentials Go to Moyasar Dashboard → Settings → API Keys. Copy both your **Secret Key** (starts with `sk_`) and your **Publishable Key** (starts with `pk_`). Both are required. Go to Moyasar Dashboard → Settings → Webhooks. Create or select your webhook endpoint and copy the webhook secret (used to verify that events are from Moyasar). ### Required Webhook Events Configure the following events so Flexprice can update invoice status when payments occur: | Event Type | Purpose | | -------------- | ------------------------------------ | | `payment_paid` | Track successful payment completions | ### Webhook configuration endpoint URL In the Moyasar Dashboard, use this Flexprice webhook URL when creating your webhook. ``` https://api.cloud.flexprice.io/v1/webhooks/moyasar/tenant_YOUR_TENANT_ID/env_YOUR_ENV_ID ``` *(If you use a different Flexprice region, use the base URL for your region instead of `https://api.cloud.flexprice.io`.)* Moyasar Connection Setup ### Setting Up the Webhook Go to Moyasar Dashboard → Settings → Webhooks and click **+ Add New Webhook**. Enter the Flexprice webhook endpoint URL (see **Webhook configuration endpoint URL** above) and select `payment_paid`. Click **Create Webhook**, then copy the **Webhook Secret** and save it for your Flexprice connection. ## Create Moyasar Connection ### Using Flexprice Dashboard Go to **Flexprice dashboard** → **Integrations** → **Moyasar** → **Add a connection**. Enter your Secret Key, Publishable Key, and Webhook Secret. Click to create the connection. Flexprice will store your credentials securely. Moyasar Connection Setup ## Connection Configuration ### Default Currency (required in Moyasar) The default currency for Moyasar is **SAR** (Saudi Riyal). * **Supported Currencies**: SAR, USD, EUR, KWD, etc. * **Currency Precision**: Moyasar requires amounts in the **smallest currency unit** (e.g., Halalah for SAR). Flexprice handles this conversion automatically. ### Currency Precision Table | Currency | Precision | Unit | Example | | -------- | --------- | ------- | ------------------ | | SAR | 2 | Halalah | 10.00 SAR = 1000 | | USD | 2 | Cents | 10.00 USD = 1000 | | KWD | 3 | Fils | 10.000 KWD = 10000 | | JPY | 0 | Yen | 1000 JPY = 1000 | ## Security Best Practices Webhooks must use HTTPS. Always configure the `webhook_secret` to enable HMAC-SHA256 signature verification. Rotate API keys periodically from the Moyasar dashboard. ## Troubleshooting | Issue | Cause | Solution | | ---------------------------- | ---------------------- | ------------------------------------------------- | | Connection test fails | Invalid `Secret Key` | Verify the key in Moyasar Dashboard. | | Webhook not updating invoice | Event type mismatch | Ensure specific `payment_paid` event is selected. | | Webhook signature error | Wrong `Webhook Secret` | Update the secret in Flexprice connection. | | Invoice not syncing | Missing credentials | Ensure connection is active and valid. | # Moyasar Invoice Synchronization Source: https://docs.flexprice.io/integrations/moyasar/invoice-sync Complete guide to invoice synchronization between Flexprice and Moyasar ## Overview Moyasar integration in Flexprice syncs invoices to Moyasar and generates a payment link (Moyasar invoice URL) for each synced invoice. ## Synchronization Flow The sync process is **one-way** (Flexprice → Moyasar) and works as follows: ```mermaid theme={null} graph LR A[Flexprice
Invoice] -->|Sync Request| B[Moyasar
API] B -->|Create Invoice| C[Moyasar
Invoice] C -->|Return URL| A C -->|Payment Link| D[Customer
Payment] D -->|Webhook (payment_paid)| E[Flexprice
Updates Status] ``` ### How it Works An invoice sync is triggered (automatically). Flexprice creates an invoice object in Moyasar using the `secret_key`. Moyasar returns a `url` (the payment page). Flexprice saves the Moyasar invoice ID and payment URL in the invoice metadata. ## Invoice Data Model ### Flexprice Invoice Structure ```json theme={null} { "id": "inv_123", "amount": "100.00", "currency": "SAR", "metadata": { "moyasar_invoice_id": "inv_moy_xyz", "moyasar_invoice_url": "https://moyasar.com/i/inv_moy_xyz" } } ``` ### Moyasar Invoice Structure ```json theme={null} { "id": "inv_moy_xyz", "status": "initiated", "amount": 10000, "currency": "SAR", "description": "Invoice for Premium Plan", "url": "https://moyasar.com/i/inv_moy_xyz", "metadata": { "flexprice_invoice_id": "inv_123" } } ``` ## Sync Configuration The following fields are mapped during sync: | Field | Flexprice → Moyasar | Notes | | ------------- | ------------------- | -------------------------------------------- | | `amount` | ✅ | Converted to smallest unit (e.g., Halalah) | | `currency` | ✅ | Passed as-is (e.g., SAR) | | `description` | ✅ | Summary of line items | | `metadata` | ✅ | Includes `flexprice_invoice_id` for tracking | ## Payment Flow & Webhooks ### Payment Journey User clicks the `moyasar_invoice_url`. Payment is processed on Moyasar's hosted page. Moyasar sends a `payment_paid` webhook. Flexprice receives the webhook, finds the invoice via the metadata/mapping, and updates the invoice status to `PAID`. ## Error Handling | Error | Cause | | ----------------------------------- | ------------------------------------------------------------------- | | `Moyasar connection not configured` | No active Moyasar connection found for the tenant/env. | | `Currency not supported` | Invoice currency (e.g., INR) not supported by your Moyasar account. | | `Invalid amount` | Amount must be positive. | ## Testing Create a Flexprice invoice. Trigger the sync to Moyasar. Check invoice metadata for `moyasar_invoice_url`. Open the URL and pay using a **Test Card**. ### Test Cards (Moyasar Sandbox) | Number | Expiry | CVC | Result | | --------------------- | ------ | --- | ------- | | `4111 1111 1111 1111` | Future | 123 | Success | | `4000 0000 0000 0002` | Future | 123 | Failure | # Nomod Connection Setup Source: https://docs.flexprice.io/integrations/nomod/connection-setup Complete guide to setting up and configuring Nomod connections in Flexprice ## Overview A Nomod connection in Flexprice lets you: * **Sync customers** from Flexprice to Nomod * **Sync invoices** from Flexprice to Nomod * **Create payment links** for invoices so customers can pay via Nomod * **Receive payment** (e.g. link paid, invoice paid) in Flexprice * **Process payments** through Nomod’s secure checkout ## Prerequisites Before you start, have: 1. **Nomod account** – [Nomod app](https://nomod.com) ([Play Store](https://nomod.app/g) / [App Store](https://nomod.app/a)) 2. **Flexprice account** – [https://flexprice.io/](https://flexprice.io/) ## Keys you need | Key | Where to get it | What it’s for | | ------------------ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------- | | **Nomod API key** | Nomod app → **Connect and Manage Integrations** | Payment links, invoices, and Nomod integration in Flexprice | | **Zapier API key** | Nomod app - **Settings** → **Business** → **Connect apps and manage keys** → **Zapier** | Contact Flexprice team to set up the Zapier Zap and webhook configuration | ## Create Nomod Connection ### Using Flexprice Dashboard Go to **Flexprice dashboard** → **Integrations** → **Nomod** → **Add a connection**. Stripe Connection Setup ### Setup at a glance Choose a name for this Nomod connection so you can identify it in Flexprice. Get your **Nomod API key** from the Nomod app (Connect and Manage Integrations). **Invoice sync = true** means Flexprice pushes invoices to Nomod. **Invoice sync = false** means Flexprice does not push invoices to Nomod (invoices stay only in Flexprice). Use any secure random name or string for webhook verification and provide it to the Flexprice team as well. Nomod does not support webhooks. For webhook configuration, contact us. ### Why Zapier? Nomod does not support webhooks directly. Zapier offers a pre-built Nomod app for webhook events. Provide your **Zapier API key** and **webhook secret** to Flexprice so it can receive Nomod events (e.g. Link Paid, Invoice Paid). Flexprice creates and manages the Zapier connection for you. Contact the Flexprice team to set up the Zapier Zap and webhook configuration. ## Configure Connection Settings ### Editing a Connection Navigate to the Nomod integration page Find your connection in the "Connected Accounts" list Click the edit icon (pencil) Modify settings as needed Click **"Update Connection"** **Note**: API Key and Webhook Secret cannot be edited after creation for security reasons. ### Deleting a Connection Navigate to the Nomod integration page Find your connection in the "Connected Accounts" list Click the delete icon (trash) for the connection you want to remove. Confirm deletion in the dialog. The connection will be removed and Nomod payment processing for it will stop. **Warning:** Deleting a connection will disable all Nomod payment processing for that connection. ## Security Best Practices ### Credential Management 1. **Environment Separation**: Use different keys for test/production 2. **Key Rotation**: Regularly rotate your Nomod API keys 3. **Access Control**: Limit API key permissions in Nomod 4. **Encryption**: All credentials are encrypted at rest in Flexprice ## Troubleshooting ### Common Issues | Issue | Cause | Solution | | ----------------------------- | --------------------------------- | ---------------------------------------------------------------------------------- | | Connection creation fails | Invalid Nomod API key | Verify key in Nomod app → Connect and Manage Integrations | | Events not reaching Flexprice | Invalid or missing Zapier API key | Verify Zapier API key in the connection; Flexprice uses it for webhook integration | | Invoice sync fails | Sync not enabled | Enable invoice outbound sync in the connection | ## Next steps After your Nomod connection is set up: 1. **Test payment links** – Create a test payment link and confirm the flow works. 2. **Check events** – Ensure your Zapier API key is correct and events reach Flexprice (Flexprice handles the Zapier integration). 3. **Enable invoice sync** – If you want invoices pushed to Nomod, turn on **Push to Nomod** in the connection. 4. **Go live** – When ready, switch to production keys in Nomod and Flexprice. # Nomod Customer Synchronization Source: https://docs.flexprice.io/integrations/nomod/customer-sync Complete guide to customer synchronization between Flexprice and Nomod ## Overview Customer synchronization in Flexprice ensures that customers exist in Nomod before invoice sync and payment link. **Customers are synced to Nomod when invoices are being synced**, ensuring proper invoice-to-customer association in Nomod. ### Key Concepts * **Invoice- and Payment-Link-Triggered Sync**: Customers are synced only when invoice sync or payment link is triggered * **On-Demand Sync**: Sync happens automatically during invoice sync * **One-Way Sync**: Customers are synced from Flexprice to Nomod only (not bidirectional) * **Entity Integration Mapping**: Links Flexprice customers to Nomod customer IDs * **Metadata Storage**: Customer metadata stores cross-platform references * **Duplicate Prevention**: Intelligent checks prevent duplicate customer creation in Nomod ## Synchronization Flow Customer sync is **invoice- or payment-link-triggered** and happens automatically: ```mermaid theme={null} graph LR A[Invoice Sync or
Payment Link Request] --> B[Ensure Customer
Synced to Nomod] B --> C{Check
Metadata} C -->|Has ID| D[Return Existing
Customer] C -->|No ID| E[Check Integration
Mapping] E --> F{Mapping
Exists?} F -->|Yes| G[Update Customer
Metadata] F -->|No| H[Create Customer
in Nomod] G --> I[Return Customer
Info] H --> J[Update Metadata
& Mapping] J --> I I --> K[Continue Invoice Sync
or Payment Link] ``` ### When Customer Sync Happens Customer sync is triggered **only** when: * An invoice is being synced to Nomod * A payment link is requested for an invoice * Invoice outbound sync is enabled in the Nomod connection (`invoice_sync.outbound = true`) when syncing invoices * Invoice status changes from `DRAFT` to `FINALIZED` (for invoice sync) **Customer sync does NOT happen for:** * Manual customer operations * Other flows that do not involve invoice sync or payment link ## Customer Data Model ### Flexprice Customer Structure ```json theme={null} { "id": "cust_1234567890abcdef", "name": "John Doe", "email": "john@example.com", "metadata": { "nomod_customer_id": "cust_nomod123abc", "nomod_sync_timestamp": "2024-01-20T10:30:00Z" }, "environment_id": "prod", "tenant_id": "tenant_123", "status": "active", "created_at": "2024-01-20T10:30:00Z" } ``` ## Sync Configuration ### Required Fields for Sync | Field | Flexprice → Nomod | Notes | | ------- | ----------------- | ------------------------------------------------------------ | | `name` | ✅ | Customer full name (required for Nomod customer creation) | | `email` | ✅ | Primary email address (required for Nomod customer creation) | ### Optional Fields | Field | Description | | ------------- | ---------------------------- | | `phone` | Phone number (E.164 format) | | `address` | Customer address information | | `external_id` | Your internal customer ID | | `metadata` | Custom key-value pairs | ## Sync Process Details ### Invoice Sync and Payment Link Flow (Customer-Triggered) ```mermaid theme={null} graph TD A[Invoice Finalized or
Payment Link Request] --> B{Invoice Sync /
Payment Link Enabled?} B -->|No| C[Skip Sync] B -->|Yes| D[Ensure Customer
Synced to Nomod] D --> E{Check
Metadata} E -->|Has nomod_customer_id| F[Return Existing
Customer] E -->|No ID| G[Check Integration
Mapping] G --> H{Mapping
Exists?} H -->|Yes| I[Update Customer
Metadata] H -->|No| J[Create Customer
in Nomod] I --> K[Return Customer
Info] J --> L[Update Metadata
& Mapping] L --> K K --> M[Create Invoice in Nomod
or Return Payment Link] ``` ## Customer Linking ### Metadata Storage Customer linking is maintained through metadata fields: **In Flexprice Customer:** ```json theme={null} { "metadata": { "nomod_customer_id": "cust_nomod123abc", "nomod_sync_timestamp": "2024-01-20T10:30:00Z" } } ``` **In Nomod Customer:** ```json theme={null} { "metadata": { "flexprice_customer_id": "cust_1234567890abcdef", "flexprice_environment": "prod", "flexprice_sync_timestamp": "2024-01-20T10:30:00Z" } } ``` ### Link Verification You can verify customer links using the API: **Endpoint:** `GET /api/v1/customers/{customer_id}/integrations` **Headers:** ```http theme={null} Authorization: Bearer your_api_key X-Tenant-ID: your_tenant_id X-Environment-ID: your_environment_id ``` **Response:** ```json theme={null} { "customer_id": "cust_1234567890abcdef", "integrations": { "nomod": { "nomod_customer_id": "cust_nomod123abc", } } } ``` ## Error Handling ### Common Sync Errors | Error | Cause | Resolution | | ------------------------- | ------------------------ | --------------------------------------------------- | | "Customer not found" | Invalid customer ID | Verify customer exists in Flexprice | | "Email already exists" | Duplicate email in Nomod | Check for existing customer in Nomod dashboard | | "Invalid email format" | Malformed email address | Fix email format in Flexprice customer | | "Missing required fields" | Name or email missing | Ensure customer has `name` and `email` in Flexprice | | "Nomod API error" | Nomod connection issue | Check Nomod connection credentials and status | ## Testing Customer Sync ### Test Environment Setup Configure Nomod connection with test API keys (`sk_test_...`) Create test customers in Flexprice with `name` and `email` (no sync yet) Customers should NOT exist in Nomod initially ### Invoice Sync Testing Flow Add a customer in Flexprice with name and email (no sync yet) Customer should NOT exist in Nomod initially Ensure invoice outbound sync is enabled in the Nomod connection Create an invoice for the customer and finalize it (status changes to `FINALIZED`) Customer should now exist in Nomod with linked metadata (`nomod_customer_id` in Flexprice customer metadata) Create another invoice for the same customer and verify it uses the existing Nomod customer (no duplicate created) # Nomod Payment Links & Invoice Sync Source: https://docs.flexprice.io/integrations/nomod/payment-links Complete guide to creating payment links and handling the Nomod checkout flow ## Overview Payment links in Flexprice provide a secure, hosted checkout experience powered by Nomod. Customers click a payment URL and are redirected to Nomod's secure checkout page to complete their payment. ### Key Features * **Secure checkout**: Hosted by Nomod with PCI compliance. * **Multiple Payment Methods**: Supports various payment methods available in Nomod * **Real-time Updates**: Webhook-driven status updates * **Auto-generated Links**: Nomod automatically generates payment link URLs ## Payment Link Lifecycle ```mermaid theme={null} graph TD A[Create Payment Request] --> B[Validate Invoice
& Customer] B --> C[Create Payment Record
INITIATED] C --> D[Generate Nomod
Payment Link] D --> E{Link Created?} E -->|Success| F[Update Status
PENDING] E -->|Failure| G[Keep Status
INITIATED] F --> H[Return Payment URL] H --> I[Customer Clicks Link] I --> J[Nomod Checkout] J --> K[Enter Payment Details] K --> L{Payment
Successful?} L -->|Yes| M[Webhook:
payment_link.paid] L -->|No| N[Webhook:
payment_link.unpaid] M --> O[Status: SUCCEEDED] O --> Q[Reconcile Invoice] ``` ## Payment Status Flow ### Status Transitions ``` INITIATED → PENDING → SUCCEEDED ``` ### Status Definitions | Status | Description | Triggers | | ----------- | -------------------------------------------------- | ---------------------------------- | | `INITIATED` | Payment record created, Nomod link not yet created | Initial payment creation | | `PENDING` | Nomod payment link created successfully | Successful Nomod API call | | `SUCCEEDED` | Payment completed successfully | Nomod webhook: `payment_link.paid` | ## Creating Payment Links ### Prerequisites 1. **Nomod Connection**: Active Nomod connection configured 2. **Invoice Status**: Invoice must be finalized and unpaid 3. **Valid Amount**: Payment amount must match invoice outstanding 4. **Connection Status**: Connection must be in "Published" status ### Recording Payments with Nomod You can use Nomod to generate payment links when recording payments: Navigate to the invoice or customer page, click **"Record Payment"**, and enter the payment amount. In the **Payment Method** dropdown, select **"Payment Link"**. In the **Payment Provider** dropdown, select your Nomod connection. Optionally add a description. Click **"Record"** to create the payment. A payment link will be generated via Nomod and a success dialog will display the payment URL. You can copy the URL to share with the customer, open the link in a new tab, or close the dialog. Share the payment link with your customer. The customer clicks the link and completes payment through Nomod. Payment status updates automatically via webhooks. ### API Request **Endpoint:** `POST /api/v1/payments` **Headers:** ```http theme={null} Content-Type: application/json Authorization: Bearer your_api_key X-Environment-ID: your_environment_id ``` **Request Body:** ```json theme={null} { "amount": 100.00, "currency": "USD", "destination_id": "inv_123", "destination_type": "invoice", "payment_method_type": "payment_link", "payment_gateway": "nomod", "metadata": { "connection_id": "conn_123", "connection_name": "Nomod Production", "success_url": "https://app.flexprice.com/billing/invoices/inv_123", "cancel_url": "https://app.flexprice.com/billing/invoices/inv_123" } } ``` ### Response ```json theme={null} { "id": "pay_1234567890abcdef", "payment_status": "pending", "payment_url": "https://nomod.com/pay/abc123", "amount": "100.00", "currency": "USD", "gateway_tracking_id": "plink_1234567890abcdef", "created_at": "2024-01-20T10:30:00Z", "metadata": { "nomod_payment_link_id": "plink_1234567890abcdef" } } ``` **Note**: The `payment_url` is automatically generated by Nomod and returned as a URL. ### Payment Link Configuration ### Required Fields | Field | Type | Required | Description | | --------------------- | ------ | -------- | ------------------------------------------ | | `destination_type` | string | ✅ | Must be `"invoice"` | | `destination_id` | string | ✅ | Valid invoice ID | | `payment_method_type` | string | ✅ | Must be `"payment_link"` | | `payment_gateway` | string | ✅ | Must be `"nomod"` | | `amount` | string | ✅ | Payment amount (must match invoice) | | `currency` | string | ✅ | Valid currency code (USD, etc.) | | `success_url` | string | ❌ | Callback URL after payment (optional) | | `cancel_url` | string | ❌ | Callback URL after cancellation (optional) | ## Payment Link Features * **Secure**: All payments processed through Nomod's secure infrastructure * **Multiple Payment Methods**: Supports various payment methods available in Nomod * **Automatic Updates**: Payment status updates automatically when customer pays * **Success/Cancel URLs**: Automatically configured to redirect back to Flexprice ### API reference **Payment Link:** [Create a new payment](https://docs.flexprice.io/api-reference/payments/create-payment#create-a-new-payment) ### Invoice Sync Whenever an invoice is generated in Flexprice it's synced to Nomod in realtime and Nomod generates a payment link for the invoice. 1. **Payment Link Reuse**: If a payment link is requested for a synced invoice, the existing Nomod invoice payment URL is returned 2. **Automatic Association**: Payments made through the URL are automatically associated with the invoice 3. **Metadata**: Once synced, you get the Nomod invoice details and payment URL in the Flexprice invoice metadata ### Invoices are automatically synced to Nomod when: * Invoice is finalized (status changes from `DRAFT` to `FINALIZED`) * Invoice outbound sync is enabled in the Nomod connection * Customer is successfully synced to Nomod (if applicable) Invoice sync to Nomod **API reference:** [Get an invoice by ID](https://docs.flexprice.io/api-reference/invoices/get-invoice) ## Error Handling ### Common Payment Errors | Error | Cause | Resolution | | ------------------------------ | ------------------------ | --------------------------------------- | | "Payment links not generating" | Connection not published | Verify connection status is "Published" | | "Invalid invoice amount" | Amount mismatch | Ensure amount matches invoice | | "Invoice already paid" | Invoice status issue | Check invoice payment status | | "Nomod link creation failed" | API error | Check Nomod connection | ## Security Considerations ### Payment Link Security * **HTTPS Required**: All payment links use HTTPS * **Link Expiry**: Links expire based on Nomod settings * **One-time Use**: Each link can only be used once * **Signature Verification**: All webhooks verified ### Data Protection * **PCI Compliance**: Nomod handles sensitive payment data * **No Card Storage**: Card details never stored in Flexprice * **Encrypted Storage**: All payment data encrypted at rest ## Webhook Endpoints * `POST /api/v1/webhooks/nomod/{tenant_id}/{environment_id}` - Nomod webhook handler For complete API documentation, see the [API Reference](/api-reference/payments/create-payment). # Paddle Connection Setup Source: https://docs.flexprice.io/integrations/paddle/connection-setup Complete guide to setting up and configuring Paddle connections in Flexprice ## Overview A Paddle connection in Flexprice stores encrypted credentials that allow the system to interact with your Paddle account for: * Creating customers and addresses in Paddle * Syncing subscriptions to Paddle for payment method collection via hosted checkout * Syncing invoices to Paddle as transactions for automatic charging * Receiving webhook notifications from Paddle * Automatic reconciliation of payments between Paddle and Flexprice ## Prerequisites Before setting up your Paddle connection, ensure you have: 1. **Active Paddle Billing Account** - Sign up at [paddle.com](https://www.paddle.com) 2. **API Key** - Available in your Paddle Dashboard (Developers → Authentication) 3. **Webhook Secret** - Configured in Paddle Dashboard (Developers → Notifications) 4. **Client-Side Token** - For Paddle.js when a Flexprice-hosted checkout page loads with a signed `token` query param (see Step 2); customers saving a card via the subscription API use `paddle_checkout_url` from the [integration workflow](/integrations/paddle/integration-workflow) 5. **Flexprice Environment** - Valid Flexprice environment 6. **Default Payment Link** - Required by Paddle on your seller account; set the Flexprice checkout base URL in Paddle (see Step 2) 7. **Domain Approval** - flexprice.io domain approved in Paddle (see Step 2) ## Step 1: Gather Paddle Credentials ### Required Credentials | Credential | Location in Paddle Dashboard | Required | Purpose | | --------------------- | --------------------------------------------- | ----------- | ---------------------------------------------------------------------------- | | **API Key** | Developers → Authentication | ✅ | API authentication | | **Webhook Secret** | Developers → Notifications → \[Your Endpoint] | ✅ | Webhook signature verification | | **Client-Side Token** | Developers → Authentication | ✅ | Paddle.js on hosted checkout when the URL includes Flexprice’s `token` param | | **Redirect URL** | Connection metadata (not Paddle) | Recommended | Success URL after payment | ### Finding Your Credentials 1. **API Key:** * Go to Paddle Dashboard → Developers → Authentication * Copy your API key (use `pdl_sdbx_` prefix for sandbox, live prefix for production) 2. **Webhook Secret:** * Go to Paddle Dashboard → Developers → Notifications * Create or select your webhook endpoint * Copy the webhook secret (used for signature verification) 3. **Client-Side Token:** * Go to Paddle Dashboard → Developers → Authentication * Copy the client-side token (e.g. `test_xxx` or `live_xxx`) for Paddle.js Paddle Authentication - API Keys and Client-Side Tokens ## Step 2: Configure Paddle Checkout & Webhooks ### Default Payment Link Paddle requires a default payment link on your seller account to create transactions. Flexprice still relies on this as part of the integration. **Card capture for subscriptions:** In the current integration, you usually send customers the **`paddle_checkout_url`** returned when you create a Flexprice subscription (see [Paddle Integration Workflow](/integrations/paddle/integration-workflow)). The steps below configure Paddle’s **required** default payment link and domain — they do not replace sharing that API-provided checkout URL. 1. Go to Paddle Dashboard → Checkout → Checkout settings 2. Set **Default payment link** to: `https://admin.flexprice.io/checkout` 3. Submit your domain for approval via **Request website approval** if not already approved Paddle Default Payment Link ### Website Approval Your domain must be approved before checkout works in production. 1. Go to Paddle Dashboard → Checkout → Request website approval 2. Add and approve the flexprice.io domain (or your production domain) Paddle Website Approval ### Setting Up the Webhook 1. Go to Paddle Dashboard → Developers → Notifications 2. Click **Add notification destination** 3. Enter your webhook URL (see format below) 4. Select the required events listed below 5. Copy the **Webhook Secret** for your connection **Webhook URL Format:** ``` https://api.cloud.flexprice.io/v1/webhooks/paddle/tenant_01K1TJDVNSN7TWY8CZY870QMNV/env_01K1TJJF0CJR410C6QVPYQTNV0 ``` *(Replace `tenant_01K1TJDVNSN7TWY8CZY870QMNV` and `env_01K1TJJF0CJR410C6QVPYQTNV0` with your tenant and environment IDs. If you use a different Flexprice region, use the base URL for your region instead of `https://api.cloud.flexprice.io`.)* **Required Webhook Events** – Configure these events in your Paddle webhook endpoint: | Event Type | Purpose | | ------------------------ | ------------------------------------------------------------- | | `transaction.completed` | Reconcile invoice payments when Paddle charges the saved card | | `subscription.activated` | Detect when a customer has saved their payment method | Paddle Webhook Configuration ## Step 3: Create Paddle Connection ### Using Flexprice Dashboard You can create a Paddle connection directly from the Flexprice dashboard: Go to **Flexprice dashboard** → **Integrations** → **Paddle** → **Add a connection**. Enter your API Key, Webhook Secret, and Client-Side Token (recommended). Optionally add a Redirect URL in metadata for post-payment redirects. Click to create the connection. Flexprice will store your credentials securely. Paddle Connection Setup ```json theme={null} { "name": "Paddle Production", "provider_type": "paddle", "encrypted_secret_data": { "api_key": "pdl_live_...", "webhook_secret": "...", "client_side_token": "live_..." }, "metadata": { "redirect_url": "https://your-app.com/billing/success" } } ``` ## Subscriptions, invoices, and Paddle 1. **Subscription setup:** When you create a Flexprice subscription, Flexprice creates a matching Paddle subscription and returns checkout metadata so the customer can save a payment method. See [Paddle Integration Workflow](/integrations/paddle/integration-workflow) for the full flow (`paddle_checkout_url`, `subscription.activated`, card capture, and **customer/address sync**). 2. **Invoicing:** Flexprice syncs each invoice to Paddle as a transaction. Paddle charges the saved payment method automatically; `transaction.completed` reconciles the invoice in Flexprice. 3. **Tax:** If Paddle adds tax on top of the invoice amount and the charged total exceeds the invoice total, Flexprice may mark the invoice as **overpaid** — align tax mode (internal vs. external) in Paddle with your pricing setup. ## Security Best Practices ### Credential Management 1. **Environment Separation**: Use sandbox keys (`pdl_sdbx_`) for development 2. **Key Rotation**: Regularly rotate your Paddle API keys 3. **Encryption**: All credentials are encrypted at rest in Flexprice ### Webhook Security 1. **HTTPS Only**: Always use HTTPS for webhook endpoints 2. **Signature Verification**: Flexprice verifies all webhook signatures 3. **Secret Management**: Keep webhook secrets secure and rotate regularly ## Troubleshooting | Issue | Cause | Solution | | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | No `paddle_checkout_url` in subscription metadata | Subscription not linked to Paddle or no active connection | Confirm an active Paddle connection under **Integrations → Paddle**; follow [Paddle Integration Workflow](/integrations/paddle/integration-workflow) | | Invoice sync fails – "Paddle address ID not found" | Customer missing country or address not synced | Add country to customer address; ensure address has valid country code | | Connection test fails | Invalid API keys | Verify keys in Paddle Dashboard | | Webhook not received (`transaction.completed` / `subscription.activated`) | Incorrect webhook URL or required events not selected | Check URL format; in Paddle → Developers → Notifications, select both required events | | Paddle.js won’t load or hosted checkout page errors | Missing **Client-Side Token** on the connection, or the URL lost Flexprice’s `token` query param | Add **Client-Side Token** in the connection. For subscriptions, customers should open **`paddle_checkout_url`** from the API ([workflow](/integrations/paddle/integration-workflow)). For other Flexprice-hosted checkout links, keep the `token` param for Paddle.js. | | Invoice marked overpaid | Paddle added tax on top of invoice amount | Align tax mode (internal vs external) in Paddle with your invoice pricing | ## Next Steps After setting up your Paddle connection: 1. **Follow the Integration Workflow**: See the [Paddle Integration Workflow](/integrations/paddle/integration-workflow) for the complete end-to-end guide — subscription creation, customer checkout, and invoice auto-charge. 2. **Test in Sandbox**: Use `pdl_sdbx_` keys and Paddle's sandbox environment before going live. 3. **Test customer sync and checkout**: Create a test customer with address, sync an invoice, and complete a test payment via Flexprice Checkout. 4. **Monitor Webhooks**: Ensure `transaction.completed` and `subscription.activated` events are being received. 5. **Go Live**: Switch to production keys when ready. For detailed API documentation, see the [API Reference](/api-reference/introduction). # Paddle Integration Workflow Source: https://docs.flexprice.io/integrations/paddle/integration-workflow End-to-end guide to the Paddle integration: subscription sync, customer checkout, and invoice auto-charge ## Overview This guide walks you through the complete Paddle integration workflow in Flexprice. **Division of responsibilities:** * **Flexprice** handles usage metering, billing logic, plan configuration, and invoice generation. * **Paddle** handles payment method collection, secure card storage, and automatic charging. **What you'll achieve:** * Subscriptions created in Flexprice automatically sync to Paddle as \$0 subscriptions * Customers save their payment method via a Paddle-hosted checkout overlay * Every invoice Flexprice generates is automatically charged against the saved card via Paddle ## Prerequisites Before following this guide: 1. Complete the [Paddle Connection Setup](/integrations/paddle/connection-setup) — you need an active Paddle connection in your Flexprice environment. 2. Ensure your Paddle webhook destination is configured with both required events: `transaction.completed` and `subscription.activated`. *** ## How It Works The integration runs in two phases: **Phase 1 — Subscription & Card Setup** ```mermaid theme={null} graph TD A[Create Flexprice subscription] --> B["Flexprice creates a zero-amount Paddle subscription"] B --> C["paddle_checkout_url and paddle_transaction_id in metadata"] C --> D[Share checkout URL with customer] D --> E[Customer saves card via Paddle overlay] E --> F[Paddle fires subscription.activated webhook] F --> G[Card is on file — ready to charge] ``` **Phase 2 — Invoice Auto-Charge** ```mermaid theme={null} graph TD A[Flexprice billing cycle runs] --> B[Invoice generated] B --> C[Invoice synced to Paddle as a transaction] C --> D[Paddle auto-charges saved card] D --> E[Paddle fires transaction.completed webhook] E --> F[Flexprice reconciles invoice as paid] ``` *** ## Step 1: Create a Subscription Create a subscription in Flexprice as you normally would. Flexprice automatically creates a corresponding \$0 subscription in Paddle and returns the checkout URL in the response metadata. ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "cust_01abc", "plan_id": "plan_01xyz", "currency": "USD", "billing_cadence": "RECURRING", "billing_period": "MONTHLY", "billing_period_count": 1, "start_date": "2025-06-01T00:00:00Z" }' ``` **Response** (abbreviated): ```json theme={null} { "id": "sub_01def456", "status": "active", "customer_id": "cust_01abc", "plan_id": "plan_01xyz", "metadata": { "paddle_checkout_url": "https://checkout.paddle.com/checkout/custom/...", "paddle_transaction_id": "txn_01abc123def" } } ``` The same metadata fields are also included in the `subscription.created` webhook payload. `paddle_checkout_url` and `paddle_transaction_id` are only present when your Flexprice environment has an active Paddle connection. If these fields are missing, verify the connection in **Integrations → Paddle**. ### With a Trial Period To give the customer a free trial before billing begins, pass `trial_period_days`: ```bash theme={null} curl -X POST https://api.flexprice.io/v1/subscriptions \ -H "Authorization: Bearer YOUR_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "customer_id": "cust_01abc", "plan_id": "plan_01xyz", "currency": "USD", "billing_cadence": "RECURRING", "billing_period": "MONTHLY", "billing_period_count": 1, "start_date": "2025-06-01T00:00:00Z", "trial_period_days": 14 }' ``` The `paddle_checkout_url` is still returned immediately. Prompt the customer to save their card during the trial window — if no card is on file when the trial ends and the first invoice is generated, the auto-charge will fail. For full details on trial behavior, see [Trialing](/docs/subscriptions/workflows/trialing). *** ## Step 2: Customer Saves Their Card Extract `paddle_checkout_url` from the subscription response and share it with your customer — embed it in your app, send it via email, or redirect to it after signup. When the customer opens the URL, they see a Paddle-hosted overlay to enter and save their payment method. No card data passes through Flexprice or your servers. Once the card is saved: * Paddle fires a `subscription.activated` webhook to your Flexprice webhook endpoint. * Flexprice records that the payment method is on file. * The customer is ready to be charged automatically on the next invoice. The customer should save their card before Flexprice raises the first invoice, or the auto-charge will fail. `subscription.activated` here is a **Paddle webhook event** — it fires when the Paddle-side \$0 subscription is activated after card capture. This is separate from the Flexprice `subscription.activated` event, which fires when a Flexprice subscription transitions from `trialing` to `active`. *** ## Step 3: Invoice Auto-Charge No action is required from you for recurring charges. When Flexprice generates an invoice on the billing cycle: 1. The invoice is synced to Paddle as a transaction against the customer's account. 2. Paddle automatically charges the saved card. 3. Paddle fires a `transaction.completed` webhook to Flexprice. 4. Flexprice marks the invoice as paid and reconciles the amounts. If Paddle applies tax on top of the invoice amount and the charged total exceeds the invoice total, Flexprice marks the invoice as **overpaid**. To avoid this, align your Paddle tax mode (internal vs. external) with your Flexprice pricing setup. *** ## Customer & Address Sync Customer sync is **on-demand** and **Flexprice → Paddle** only. Customers are synced to Paddle automatically when a subscription or invoice operation requires it. Paddle requires a customer address (at minimum: a country code) to create transactions. If a customer is missing a country, invoice sync will fail with `"Paddle address ID not found"`. Add the country to the customer record in Flexprice before creating a subscription. *** ## Troubleshooting | Issue | Cause | Solution | | -------------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | No `paddle_checkout_url` in subscription metadata | No active Paddle connection in environment | Verify connection in **Integrations → Paddle** | | `transaction.completed` webhook not received | Webhook destination not configured or wrong events | Go to Paddle → Developers → Notifications and confirm both `transaction.completed` and `subscription.activated` are selected | | Invoice not auto-charged | Customer hasn't saved card yet (`subscription.activated` not received) | Share the `paddle_checkout_url` with the customer; wait for card save before expecting auto-charges | | Invoice sync fails — "Paddle address ID not found" | Customer missing country on their address | Add country to the customer's address in Flexprice | | Invoice marked overpaid | Paddle applied tax on top of the invoice amount | Align tax mode (internal vs. external) in Paddle settings with your Flexprice pricing | | Checkout URL expired or invalid | Paddle transaction expired | Re-create the subscription or contact Flexprice support to re-sync the Paddle subscription | # QuickBooks Connection Setup Source: https://docs.flexprice.io/integrations/quickbooks/connection-setup Complete guide to setting up and configuring QuickBooks Online connections in Flexprice ## Overview A QuickBooks connection in Flexprice stores encrypted credentials that allow the system to interact with your QuickBooks Online account for: * Syncing invoices from Flexprice to QuickBooks * Creating customers in QuickBooks automatically * Creating items (products/services) for invoice line items * Inbound Payment sync ## Understanding Sync Configuration ### Invoice Sync (Outbound Only) When enabled, invoices created in Flexprice are automatically synced to QuickBooks: * **Outbound**: `true` - Sync invoices from Flexprice to QuickBooks * **Inbound**: Always `false` - QuickBooks invoices are NOT imported to Flexprice ### Payment Sync (Inbound Only) When enabled, payment notifications from QuickBooks update invoice status in Flexprice: * **Inbound**: `true` - Receive payment notifications from QuickBooks webhooks * **Outbound**: Always `false` - Payments are NOT sent from Flexprice to QuickBooks **Important**: Payments are recorded in QuickBooks by you or your accounting team. Flexprice only receives notifications when payments are marked as complete. ## Prerequisites Before setting up your QuickBooks connection, ensure you have: 1. **Intuit Developer Account** - Create an app at [developer.intuit.com](https://developer.intuit.com) 2. **OAuth 2.0 Credentials** - Client ID and Client Secret from your app 3. **Income Account in QuickBooks** - At least one active Income account (for creating items) ## Step 1: Create a QuickBooks App ### Create App in Intuit Developer Portal 1. Go to [Intuit Developer Portal](https://developer.intuit.com/app/developer/myapps) 2. Click **Create an app** 3. Select **QuickBooks Online and Payments** 4. Fill in app details: * **App Name**: "Flexprice Connection" (or your preferred name) following the naming quickbooks' naming conventions. Go to [View naming guidelines](https://developer.intuit.com/app/developer/qbo/docs/go-live/list-on-the-app-store/naming-and-logo-guidelines) 5. Define the authorization scope of your app * You will be provided with two options (you can select either or both as per your requirements): * [com.intuit.quickbooks.accounting](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/account) * [com.intuit.quickbooks.payment](https://developer.intuit.com/app/developer/qbpayments/docs/api/resources/all-entities/bankaccounts) 6. Click **Create app** ### Configure OAuth Settings 1. In your app dashboard, go to **Settings** 2. Configure redirect URIs: * Add flexprice resource `https://admin.flexprice.io/tools/integrations/oauth/callback` as one of your redirect uri 3. Go to **Keys & Credentials** and copy your credentials: * **Client ID** * **Client Secret** ## Step 2: Configure Webhook (Optional but Recommended) Webhooks allow Flexprice to receive real-time payment notifications from QuickBooks. ### Webhook URL Format ``` https://api.cloud.flexprice.io/v1/webhooks/quickbooks/{tenant_id}/{environment_id} ``` ### Setting Up Webhooks 1. Go to Intuit Developer Portal → Your App → **Webhooks** 2. Set up your webhook endpoint to receive live events. * Enter the webhook URL you were provided with from flexprice 3. Subscribe to these events: * `Payment` entity (Create) 4. Copy the **Webhook Verifier Token** 5. Click **Save** **Note**: Webhook verifier token is used to verify webhook signatures for security. ## Step 3: Create QuickBooks Connection ### Using Flexprice Dashboard You can create a QuickBooks connection through OAuth flow in the Flexprice dashboard: Go to **Settings** → **Integrations** → **QuickBooks** QuickBooks Connection Setup Fill in the connection form: * **Connection Name**: "QuickBooks Production" (or your preferred name) * **Client ID**: From Intuit Developer Portal * **Client Secret**: From Intuit Developer Portal * **Environment**: Select "Production" or "Sandbox" * **Webhook Verifier Token**: From webhook configuration (optional) * **Income Account ID**: Provide any custom income account id that you may have created to track your revenue, by default it is set to `79` (used for creating items) Enable sync options: * **Invoice Sync**: Enable to automatically sync invoices to QuickBooks * **Payment Sync**: Enable to sync payments from QuickBooks Click **Connect to QuickBooks** to start OAuth flow: 1. You'll be redirected to QuickBooks login 2. Sign in to your QuickBooks account 3. Select the company to connect 4. Authorize Flexprice to access your account 5. You'll be redirected back to Flexprice After authorization, verify the connection is active and working ## Security Best Practices ### Credential Management 1. **Environment Separation**: Use sandbox for development, production for live data 2. **Token Refresh**: Access tokens expire after 1 hour - Flexprice automatically refreshes them 3. **Access Control**: Limit OAuth app permissions in QuickBooks 4. **Encryption**: All credentials are encrypted at rest in Flexprice ### Webhook Security 1. **HTTPS Only**: Always use HTTPS for webhook endpoints in production 2. **Signature Verification**: Configure webhook verifier token for signature validation 3. **Idempotency**: Flexprice handles duplicate webhook events automatically 4. **Rate Limiting**: Webhooks are rate-limited to prevent abuse ### OAuth Token Management **Access Token Expiration**: * Access tokens expire after **1 hour** of inactivity * Flexprice automatically refreshes tokens using refresh tokens * No manual intervention required **Refresh Token Expiration**: * Refresh tokens expire after **100 days** of inactivity * If both tokens expire, you'll need to re-authorize the connection ## Troubleshooting ### Common Issues | Issue | Cause | Solution | | ------------------------- | ---------------------- | ------------------------------------------------------ | | Connection test fails | Invalid credentials | Verify Client ID and Secret in Intuit Developer Portal | | OAuth authorization fails | Incorrect redirect URI | Check redirect URI matches in app settings | | Invoice sync fails | Missing Income Account | Ensure Income Account ID is configured | | Webhook not received | Incorrect webhook URL | Verify webhook URL format and subscription | | Token expired | 1 hour | Re-authorize the connection | ### Debug Steps 1. **Check Intuit Developer Portal**: Verify OAuth credentials and webhook configuration 2. **Verify Realm ID**: Ensure Realm ID matches your QuickBooks company 3. **Test Income Account**: Query QuickBooks to ensure Income Account exists and is active 4. **Monitor Webhooks**: Check webhook delivery logs in Intuit Developer Portal 5. **Review Logs**: Check Flexprice application logs for sync errors ## Income Account Configuration QuickBooks requires all service items to have an Income Account reference. This is where revenue is tracked in your books. ### Finding Your Income Account ID 1. Go to QuickBooks → **Chart of Accounts** 2. Look for accounts with type **Income** 3. Click on an account to view details ### Setting Default Income Account During connection setup, you may provide an Income Account ID. This account will be used as the default for all items created from Flexprice line items. ## Next Steps After setting up your QuickBooks connection: 1. **Test Invoice Sync**: Create a test invoice in Flexprice and verify it appears in QuickBooks 2. **Verify Customer Creation**: Ensure customers are automatically created when needed 3. **Check Item Creation**: Verify line items are creating service items in QuickBooks 4. **Configure Webhooks**: Set up payment webhooks for automated reconciliation 5. **Monitor Sync Status**: Check integration logs for any sync errors ## Related Documentation * [Item Sync](/integrations/quickbooks/item-sync) - Learn how items are synced to QuickBooks * [Integration Workflow](/integrations/quickbooks/integration-workflow) - Understand the complete sync workflow ## Additional Resources * [QuickBooks API Documentation](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/account) * [OAuth 2.0 Guide](https://developer.intuit.com/app/developer/qbo/docs/develop/authentication-and-authorization) * [Webhook Documentation](https://developer.intuit.com/app/developer/qbo/docs/develop/webhooks) # QuickBooks Integration Workflow Source: https://docs.flexprice.io/integrations/quickbooks/integration-workflow Complete end-to-end workflow for syncing invoices from Flexprice to QuickBooks Online ## Overview This guide walks through the complete integration workflow between Flexprice and QuickBooks Online, from initial setup to invoice sync and payment reconciliation. **Integration Type**: One-way sync (Flexprice → QuickBooks) **Key Capabilities**: * ✅ Automatic invoice sync * ✅ On-demand customer creation * ✅ Automatic item (product/service) creation * ✅ Inbound Payment sync ## Step-by-Step Workflow ### Phase 1: Initial Setup 1. Go to [Intuit Developer Portal](https://developer.intuit.com) 2. Create new app for QuickBooks Online 3. Configure OAuth 2.0 settings 4. Copy Client ID and Client Secret **Duration**: 5-7 minutes 1. In Intuit Developer Portal, go to Webhooks 2. Add webhook endpoint: `https://api.cloud.flexprice.io/v1/webhooks/quickbooks/{tenant_id}/{environment_id}` 3. Subscribe to Payment entity events 4. Copy webhook verifier token **Duration**: 2-3 minutes 1. Navigate to Integrations 2. Go to QuickBooks and click "Add a connection" 3. Enter credentials and configuration 4. Complete OAuth authorization flow 5. Verify connection is active **Duration**: 3-5 minutes **Total Setup Time**: \~10 minutes (one-time) ### Phase 2: Plan and Price Setup Create a pricing plan with charges: **Example Plan: "Pro Plan"** * Recurring charge: \$99/month (Flat fee) * Metered charge: \$0.01 per API call (Usage-based) ```json theme={null} { "name": "Pro Plan", "interval": "month", "prices": [ { "id": "price_recurring_01", "display_name": "Monthly Fee", "amount": 99.00, "type": "recurring" }, { "id": "price_metered_02", "display_name": "API Calls", "unit_amount": 0.01, "meter": "api_calls", "type": "usage" } ] } ``` Verify QuickBooks connection: * Status: Active * Invoice sync: Enabled * Income Account: Configured **No action needed** - Items will be created automatically during first invoice sync ### Phase 3: Customer Subscription Customer creates a subscription to Pro Plan: ```json theme={null} { "customer_id": "cust_abc123", "plan_id": "plan_pro", "start_date": "2025-01-01" } ``` During the subscription period: * Customer uses 4,500 API calls * Usage is tracked via events * Billing period ends on 2025-01-31 ### Phase 4: Invoice Generation At end of billing period, Flexprice generates invoice: ```json theme={null} { "id": "inv_xyz789", "customer_id": "cust_abc123", "line_items": [ { "price_id": "price_recurring_01", "description": "Pro Plan - Monthly Fee", "amount": 99.00, "quantity": 1 }, { "price_id": "price_metered_02", "description": "API Calls", "amount": 45.00, "quantity": 4500, "unit_price": 0.01 } ], "total": 144.00, "status": "open" } ``` Flexprice checks if invoice should sync: 1. ✅ QuickBooks connection exists 2. ✅ Connection is active 3. ✅ Invoice sync is enabled 4. ✅ Customer has valid data → **Proceed with sync** ### Phase 5: Customer Sync Query entity\_integration\_mapping: ```sql theme={null} SELECT * FROM entity_integration_mapping WHERE entity_id = 'cust_abc123' AND entity_type = 'customer' AND provider_type = 'quickbooks' ``` **Result**: Not found → Customer needs to be created Create customer via QuickBooks API: **Request**: ```json theme={null} POST /v3/company/{realmId}/customer { "DisplayName": "Acme Corporation", "PrimaryEmailAddr": { "Address": "billing@acme.com" }, "BillAddr": { "Line1": "123 Main St", "City": "San Francisco", "CountrySubDivisionCode": "CA", "PostalCode": "94105", "Country": "USA" } } ``` **Response**: ```json theme={null} { "Id": "67", "DisplayName": "Acme Corporation", "PrimaryEmailAddr": { "Address": "billing@acme.com" } } ``` Create mapping record: ```json theme={null} { "entity_id": "cust_abc123", "entity_type": "customer", "provider_type": "quickbooks", "provider_entity_id": "67", "metadata": { "synced_at": "2025-01-31T10:00:00Z", "quickbooks_customer_display_name": "Acme Corporation", "quickbooks_customer_primary_email_addr_address": "billing@acme.com" } } ``` ### Phase 6: Item Creation **Price**: `price_recurring_01` (Monthly Fee) 1. Check mapping → Not found 2. Query QuickBooks for item → Not found 3. Create service item: ```json theme={null} POST /v3/company/{realmId}/item { "Name": "Pro Plan-Monthly Fee", "Description": "price_recurring_01", "Type": "Service", "IncomeAccountRef": { "value": "79", "name": "Services" } } ``` 4. QB Response: `{"Id": "125"}` 5. Store mapping: `price_recurring_01` → `125` **Price**: `price_metered_02` (API Calls) 1. Check mapping → Not found 2. Query QuickBooks for item → Not found 3. Create service item: ```json theme={null} POST /v3/company/{realmId}/item { "Name": "Pro Plan-API Calls", "Description": "price_metered_02", "Type": "Service", "IncomeAccountRef": { "value": "79", "name": "Services" } } ``` 4. QB Response: `{"Id": "126"}` 5. Store mapping: `price_metered_02` → `126` ### Phase 7: Invoice Sync Create invoice with all line items: ```json theme={null} POST /v3/company/{realmId}/invoice { "DocNumber": "INV-001", "TxnDate": "2025-01-31", "DueDate": "2025-02-14", "CustomerRef": { "value": "67" }, "Line": [ { "DetailType": "SalesItemLineDetail", "Amount": 99.00, "Description": "Pro Plan - Monthly Fee", "SalesItemLineDetail": { "ItemRef": { "value": "125", "name": "Pro Plan-Monthly Fee" }, "UnitPrice": 99.00, "Qty": 1 } }, { "DetailType": "SalesItemLineDetail", "Amount": 45.00, "Description": "API Calls", "SalesItemLineDetail": { "ItemRef": { "value": "126", "name": "Pro Plan-API Calls" }, "UnitPrice": 0.01, "Qty": 4500 } } ], "TotalAmt": 144.00 } ``` **QB Response**: ```json theme={null} { "Id": "219", "DocNumber": "INV-001", "TotalAmt": 144.00, "Balance": 144.00 } ``` Create mapping record: ```json theme={null} { "entity_id": "inv_xyz789", "entity_type": "invoice", "provider_type": "quickbooks", "provider_entity_id": "219", "metadata": { "synced_at": "2025-01-31T10:05:00Z", "quickbooks_invoice_doc_number": "INV-001" } } ``` Update invoice metadata: ```json theme={null} { "id": "inv_xyz789", "metadata": { "quickbooks_invoice_id": "219", "quickbooks_synced_at": "2025-01-31T10:05:00Z", "quickbooks_status": "pending" } } ``` ### Phase 8: Payment Recording (in QuickBooks) Your accounting team records payment in QuickBooks: 1. Open QuickBooks Desktop/Online 2. Navigate to invoice #219 3. Click "Receive Payment" 4. Enter payment details: * Amount: \$144.00 * Date: 2025-02-05 * Method: Bank Transfer 5. Mark invoice as paid QuickBooks creates a Payment record: ```json theme={null} { "Id": "222", "TxnDate": "2025-02-05", "TotalAmt": 144.00, "CustomerRef": {"value": "67"}, "Line": [ { "Amount": 144.00, "LinkedTxn": [ { "TxnId": "219", "TxnType": "Invoice" } ] } ] } ``` ### Phase 9: Webhook Notification QuickBooks sends webhook to Flexprice: **Webhook Payload**: ```json theme={null} POST /v1/webhooks/quickbooks/{tenant_id}/{environment_id} { "eventNotifications": [ { "realmId": "1234567890", "dataChangeEvent": { "entities": [ { "name": "Payment", "id": "222", "operation": "Create", "lastUpdated": "2025-02-05T14:30:00.000Z" } ] } } ] } ``` **Headers**: ``` intuit-signature: ``` 1. Verify webhook signature (if verifier token configured) 2. Check payment sync is enabled 3. Extract payment ID: "222" ### Phase 10: Payment Reconciliation Call QuickBooks API to get full payment: ```bash theme={null} GET /v3/company/{realmId}/payment/222 ``` **Response**: ```json theme={null} { "Id": "222", "TotalAmt": 144.00, "TxnDate": "2025-02-05", "Line": [ { "Amount": 144.00, "LinkedTxn": [ { "TxnId": "219", "TxnType": "Invoice" } ] } ] } ``` **Extract**: QuickBooks Invoice ID = "219" Query entity\_integration\_mapping: ```sql theme={null} SELECT * FROM entity_integration_mapping WHERE provider_entity_id = '219' AND entity_type = 'invoice' AND provider_type = 'quickbooks' ``` **Result**: Flexprice Invoice ID = `inv_xyz789` Update invoice in Flexprice: ```json theme={null} { "id": "inv_xyz789", "payment_status": "SUCCEEDED", "paid_at": "2025-02-05T14:30:00Z", "amount_paid": 144.00, "amount_remaining": 0.00, "metadata": { "payment_method": "offline", "payment_recorded_by": "quickbooks", "quickbooks_payment_id": "222", "quickbooks_invoice_id": "219", "payment_synced_at": "2025-02-05T14:30:00Z" } } ``` Invoice is now marked as paid in Flexprice: * ✅ Payment status: SUCCEEDED * ✅ Amount paid: \$144.00 * ✅ Linked to QuickBooks payment * ✅ Audit trail maintained ## Next Invoice (Same Customer) When the next billing cycle arrives: Second invoice generated for same customer ✅ Mapping exists → Use QB Customer ID "67" (no creation needed) ✅ Mappings exist → Use QB Item IDs "125" and "126" (no creation needed) Only invoice is created in QuickBooks (reusing customer and items) **Result**: Subsequent invoices sync much faster since customer and items already exist. ## Key Integration Points ### 1. Entity Integration Mapping **Purpose**: Track relationships between Flexprice and QuickBooks entities **Entities Tracked**: * Customers: `cust_abc123` ↔ QB Customer `67` * Prices/Items: `price_recurring_01` ↔ QB Item `125` * Invoices: `inv_xyz789` ↔ QB Invoice `219` **Why It Matters**: * Prevents duplicate entity creation * Enables bidirectional lookups * Maintains data integrity ### 2. Sync Configuration **Invoice Sync**: ```json theme={null} { "invoice": { "inbound": false, "outbound": true } } ``` **Payment Sync**: ```json theme={null} { "payment": { "inbound": true, "outbound": false } } ``` ### 3. Webhook Processing **Flow**: 1. QuickBooks sends webhook → Flexprice 2. Verify signature (optional) 3. Fetch full payment details 4. Find linked invoice 5. Update invoice status 6. Return 200 OK immediately **Security**: * HMAC-SHA256 signature verification * HTTPS only * Idempotency handling * Rate limiting ## Error Handling ### Common Scenarios | Scenario | Handling | Result | | ----------------------- | ------------------------------ | ------------------- | | Customer already exists | Use existing QB customer | No error | | Item already exists | Use existing QB item | No error | | Invoice already synced | Skip sync, return mapping | No error | | Missing Income Account | Return error, block sync | User must configure | | QB API rate limit | Retry with exponential backoff | Eventual success | | OAuth token expired | Auto-refresh token, retry | Transparent to user | | Webhook signature fail | Log warning, skip processing | Security measure | ### Graceful Degradation **Philosophy**: Integration failures should NOT break core Flexprice functionality **Example**: * Invoice creation in Flexprice: ✅ Success * QB sync fails due to API error: ⚠️ Logged, not blocking * Invoice is still accessible in Flexprice * Manual retry available ## Performance Considerations ### First Invoice Sync * **Time**: \~5-10 seconds * **Operations**: Customer creation + 2 item creations + invoice creation ### Subsequent Invoice Syncs * **Time**: \~2-3 seconds * **Operations**: Invoice creation only (reuses existing customer and items) ### Optimization Strategies * ✅ Entity mapping cache * ✅ Parallel API calls where possible * ✅ Connection pooling * ✅ Automatic token refresh ## Troubleshooting Guide ### Invoice Not Syncing 1. Verify QB connection is active 2. Check OAuth token is valid 3. Test API connectivity 1. Verify invoice sync is enabled 2. Check sync\_config.invoice.outbound = true 3. Ensure no sync filters blocking 1. Verify Income Account ID is configured 2. Ensure account exists in QB and is active 3. Query QB to confirm account type ### Payment Not Updating 1. Verify webhook endpoint is correct 2. Check webhook events are subscribed 3. Review webhook delivery logs in Intuit portal 1. Verify payment sync is enabled 2. Check sync\_config.payment.inbound = true 3. Ensure webhook verifier token is configured (optional) 1. Verify invoice was synced to QB 2. Check entity\_integration\_mapping exists 3. Confirm QB invoice ID matches ## Related Documentation * [Connection Setup](/integrations/quickbooks/connection-setup) - Initial QuickBooks connection setup * [Item Sync](/integrations/quickbooks/item-sync) - Detailed item creation and mapping # Item Sync to QuickBooks Source: https://docs.flexprice.io/integrations/quickbooks/item-sync Learn how Flexprice automatically creates and manages items in QuickBooks for invoice line items ## Overview When syncing invoices from Flexprice to QuickBooks, each invoice line item must reference a QuickBooks **Item** (product or service). Flexprice automatically handles item creation and management, ensuring your line items are properly tracked in QuickBooks. **Key Points**: * Items are synced when QuickBooks connection is active * Each Flexprice price is mapped to a QuickBooks service item * Items are linked to your configured Income Account * Item mappings are stored to prevent duplicates ### When Items Are Created Items are created automatically when: 1. **New Price**: A Flexprice price hasn't been synced to QuickBooks before 2. **Active Connection**: QuickBooks connection is active and authenticated ### Why Service Items? QuickBooks has different item types (Inventory, Non-Inventory, Service). Flexprice uses **Service** items because: * ✅ Best fit for SaaS and subscription businesses * ✅ No inventory tracking required * ✅ Simplifies accounting for digital services * ✅ Requires only Income Account (no COGS account) ## Entity Integration Mapping ### Mapping Structure When an item is created, Flexprice stores a mapping record: ```json theme={null} { "entity_id": "price_01ABC123", "entity_type": "price", "provider_type": "quickbooks", "provider_entity_id": "125", "metadata": { "synced_at": "2025-11-25T09:50:21Z", "quickbooks_item_name": "Pro Plan-API Calls" } } ``` ### Why Mapping Matters **Prevents Duplicates**: * Before creating an item, Flexprice checks if mapping exists * If found, uses existing QuickBooks item ID * Avoids creating duplicate items for the same price **Enables Sync**: * Maps Flexprice prices to QuickBooks items * Allows consistent line item references across invoices * Maintains data integrity between systems ## Troubleshooting ### Common Issues | Issue | Cause | Solution | | ----------------------- | ---------------------- | ------------------------------------------ | | Item creation fails | Missing Income Account | Configure Income Account ID in connection | | Item creation fails | Invalid account ID | Verify Income Account exists and is active | | Duplicate items created | Mapping not found | Check entity\_integration\_mapping table | ## Best Practices ### Income Account 💡 **Use a Dedicated Account**: Create a specific Income account like "Flexprice Revenue" for easy tracking 💡 **Keep Account Active**: Ensure the Income account is always active in QuickBooks 💡 **Document Account ID**: Store the account ID in your configuration documentation ### Item Management 💡 **Don't Manually Edit**: Avoid manually editing Flexprice-created items in QuickBooks 💡 **Use Consistent Naming**: Follow Flexprice naming conventions for clarity 💡 **Monitor Mappings**: Regularly check entity\_integration\_mapping for data integrity ## Related Documentation * [Connection Setup](/integrations/quickbooks/connection-setup) - Set up QuickBooks connection * [Integration Workflow](/integrations/quickbooks/integration-workflow) - Complete sync workflow ## QuickBooks Resources * [QuickBooks Item API](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/most-commonly-used/item) * [QuickBooks Account API](https://developer.intuit.com/app/developer/qbo/docs/api/accounting/all-entities/account) # Razorpay Connection Setup Source: https://docs.flexprice.io/integrations/razorpay/connection-setup Complete guide to setting up and configuring Razorpay connections in Flexprice ## Overview A Razorpay connection in Flexprice stores encrypted credentials that allow the system to interact with your Razorpay account for: * Creating customers in Razorpay * Processing payment links via Razorpay Checkout * Receiving webhook notifications from Razorpay * Synchronizing customer data between systems * Syncing invoices to Razorpay for payment collection ## Prerequisites Before setting up your Razorpay connection, ensure you have: 1. **Active Razorpay Account** - Sign up at [razorpay.com](https://razorpay.com) 2. **API Keys** - Available in your Razorpay Dashboard 3. **Webhook Endpoint** - Configured in Razorpay Dashboard 4. **Flexprice Environment** - Valid tenant and environment IDs ## Step 1: Gather Razorpay Credentials ### Required Credentials | Credential | Location in Razorpay Dashboard | Required | Purpose | | ------------------ | -------------------------------------- | -------- | ------------------------------ | | **Key ID** | Settings → API Keys | ✅ | API authentication | | **Secret Key** | Settings → API Keys | ✅ | API authentication | | **Webhook Secret** | Settings → Webhooks → \[Your Endpoint] | ✅ | Webhook signature verification | ### Finding Your Credentials 1. **API Keys:** * Go to Razorpay Dashboard → Settings → API Keys * Copy your `Key ID` * Copy your `Secret Key` * Use test keys for development 2. **Webhook Secret:** * Go to Razorpay Dashboard → Settings → Webhooks * Create or select your webhook endpoint * Copy the webhook secret (used for signature verification) ## Step 2: Configure Webhook Endpoint in Razorpay ### Webhook URL Format ``` https://api.cloud.flexprice.io/v1/webhooks/razorpay/tenant_01K1TJDVNSN7TWY8CZY870QMNV/env_01K1TJJF0CJR410C6QVPYQTNV0 ``` ### Required Webhook Events Configure these events in your Razorpay webhook endpoint: | Event Type | Purpose | | ------------------------ | ----------------------------------------------------------------- | | `payment.captured` | Track successful payment completions | | `payment.failed` | Handle payment failures and errors | | `payment_link.paid` | Required for Checkout Sessions — triggers subscription activation | | `payment_link.cancelled` | Required for Checkout Sessions — triggers draft entity cleanup | | `payment_link.expired` | Required for Checkout Sessions — triggers draft entity cleanup | The `payment_link.*` events are required if you use [Flexprice Checkout Sessions](/docs/checkout/checkout-sessions). Without them, subscriptions created via Checkout will remain in `draft` status after payment. If you only use manual invoice payment links (not Checkout Sessions), these three events are optional. ### Setting Up the Webhook 1. Go to Razorpay Dashboard → Settings → Webhooks 2. Click **+ Add New Webhook** 3. Enter your webhook URL 4. Select the required events listed above 5. Click **Create Webhook** 6. Copy the **Webhook Secret** for your connection ## Step 3: Create Razorpay Connection ### Using Flexprice Dashboard You can create a Razorpay connection directly from the Flexprice dashboard: Razorpay Connection Setup ### Webhook Configuration After creating the connection, configure webhooks in the connection settings: Razorpay Webhook Configuration ### API Request ### API Request **Endpoint:** `POST /api/v1/connections` **Headers:** ```http theme={null} Content-Type: application/json Authorization: Bearer your_api_key X-Tenant-ID: your_tenant_id X-Environment-ID: your_environment_id ``` **Request Body:** ```json theme={null} { "name": "Razorpay Production", "provider_type": "razorpay", "encrypted_secret_data": { "key_id": "rzp_live_...", "secret_key": "rzp_live_...", "webhook_secret": "..." } } ``` ### Response ```json theme={null} { "id": "conn_1234567890abcdef", "name": "Razorpay Production", "provider_type": "razorpay", "status": "active", "created_at": "2024-01-20T10:30:00Z", "updated_at": "2024-01-20T10:30:00Z" } ``` ## Security Best Practices ### Credential Management 1. **Environment Separation**: Use different keys for test/production 2. **Key Rotation**: Regularly rotate your Razorpay API keys 3. **Access Control**: Limit API key permissions in Razorpay 4. **Encryption**: All credentials are encrypted at rest in Flexprice ### Webhook Security 1. **HTTPS Only**: Always use HTTPS for webhook endpoints 2. **Signature Verification**: Flexprice verifies all webhook signatures 3. **Secret Management**: Keep webhook secrets secure and rotate regularly 4. **Rate Limiting**: Implement rate limiting on webhook endpoints ## Troubleshooting ### Common Issues | Issue | Cause | Solution | | ---------------------------- | --------------------------- | ----------------------------------- | | Connection test fails | Invalid API keys | Verify keys in Razorpay Dashboard | | Webhook not received | Incorrect webhook URL | Check URL format and endpoint | | Signature verification fails | Wrong webhook secret | Update webhook secret in connection | | Customer sync fails | Missing account permissions | Check Razorpay account permissions | ### Debug Steps 1. **Check Razorpay Dashboard**: Verify API keys and webhook configuration 2. **Test API Keys**: Use Razorpay API to test key validity 3. **Monitor Webhooks**: Check webhook delivery logs in Razorpay 4. **Review Logs**: Check Flexprice application logs for errors ## Next Steps After setting up your Razorpay connection: 1. **Test Customer Sync**: Create a test customer and sync to Razorpay 2. **Create Payment Links**: Test payment link creation and flow 3. **Monitor Webhooks**: Ensure webhook events are being received 4. **Go Live**: Switch to production keys when ready # Razorpay Customer Synchronization Source: https://docs.flexprice.io/integrations/razorpay/customer-sync Complete guide to on-demand customer synchronization between Flexprice and Razorpay ## Overview Customer synchronization ensures that customers exist in both Flexprice and Razorpay with linked identifiers, enabling: * **On-demand sync**: Customers are synced to Razorpay only when needed for payments * Payment link creation for any Flexprice customer * Invoice sync to Razorpay * Consistent customer data across platforms * Efficient payment processing workflows **Note**: Customer sync is **one-way only** (Flexprice → Razorpay). Customers are automatically synced to Razorpay when: * Creating payment links * Syncing invoices to Razorpay ## Synchronization Flow Customer sync works **on-demand** and is **one-way only** (Flexprice → Razorpay): ```mermaid theme={null} graph LR A[Flexprice
Customer] -->|On-Demand
Sync| B[Razorpay
Customer] C[Payment Link
Request] -->|Triggers
Sync| B D[Invoice Sync
Request] -->|Triggers
Sync| B B -->|Notes Link| A ``` ### Key Concepts * **One-Way Sync**: Customers are synced from Flexprice to Razorpay only (not bidirectional) * **On-Demand Sync**: Customers are synced to Razorpay only when payment interactions occur * **Sync Triggers**: Sync happens automatically when creating payment links or syncing invoices * **Entity Integration Mapping**: Links Flexprice customers to Razorpay customer IDs * **Notes Storage**: Customer notes store cross-platform references for linking * **Duplicate Prevention**: Intelligent checks prevent duplicate customer creation in Razorpay * **Performance Optimization**: No unnecessary syncs for customers who never make payments ## Customer Data Model ### Flexprice Customer Structure ```json theme={null} { "id": "cust_1234567890abcdef", "metadata": { "razorpay_customer_id": "cust_razorpay123abc" }, "environment_id": "prod", "tenant_id": "tenant_123", "status": "active", "created_at": "2024-01-20T10:30:00Z" } ``` ## Sync Configuration ### Required Fields for Sync | Field | Flexprice → Razorpay | Notes | | ------- | -------------------- | --------------------- | | `name` | ✅ | Customer full name | | `email` | ✅ | Primary email address | ## Sync Process Details ### On-Demand Sync Flow (Payment-Triggered) ```mermaid theme={null} graph TD A[Payment Request] --> B[Ensure Customer
Synced to Razorpay] B --> C{Check
Metadata} C -->|Has ID| D[Return Existing
Customer] C -->|No ID| E[Check Integration
Mapping] E --> F{Mapping
Exists?} F -->|Yes| G[Update Customer
Metadata] F -->|No| H[Create Customer
in Razorpay] G --> I[Return Customer
Info] H --> J[Update Metadata
& Mapping] J --> I I --> K[Process Payment] ``` ## Customer Linking ### Notes Storage Customer linking is maintained through notes fields: **In Flexprice Customer:** ```json theme={null} { "metadata": { "razorpay_customer_id": "cust_razorpay123abc", "razorpay_sync_timestamp": "2024-01-20T10:30:00Z" } } ``` **In Razorpay Customer:** ```json theme={null} { "notes": { "flexprice_customer_id": "cust_1234567890abcdef", "flexprice_environment": "prod", "flexprice_sync_timestamp": "2024-01-20T10:30:00Z" } } ``` ## Error Handling ### Common Sync Errors | Error | Cause | Resolution | | ---------------------- | --------------------------- | --------------------------- | | "Customer not found" | Invalid customer ID | Verify customer exists | | "Email already exists" | Duplicate email in Razorpay | Check for existing customer | | "Invalid email format" | Malformed email address | Fix email format | | "Razorpay API error" | Razorpay connection issue | Check Razorpay credentials | ### Sync Failure Recovery When sync fails: 1. **Log Error**: Record failure with details 2. **Retry Logic**: Automatic retry with exponential backoff 3. **Manual Intervention**: Admin can retry failed syncs 4. **Notification**: Alert on persistent failures ## Testing Customer Sync ### Test Environment Setup 1. **Test Razorpay Account**: Use Razorpay test mode 2. **Test Customers**: Create test customers in Flexprice only 3. **Webhook Testing**: Use ngrok for local webhook testing ### On-Demand Sync Testing Flow 1. **Create Test Customer**: Add customer in Flexprice (no sync yet) 2. **Verify No Sync**: Customer should NOT exist in Razorpay initially 3. **Create Payment Link**: Trigger payment link creation 4. **Verify Auto-Sync**: Customer should now exist in Razorpay 5. **Test Invoice Sync**: Sync invoice with same customer 6. **Verify Reuse**: Should use existing Razorpay customer (no duplicate) # Razorpay Integration Workflow Source: https://docs.flexprice.io/integrations/razorpay/integration-workflow Complete guide to integrating Flexprice with Razorpay for payment processing ## Overview This comprehensive guide walks you through integrating **Flexprice** with **Razorpay** to enable seamless payment processing and advanced usage-based billing. This integration combines Flexprice's powerful usage tracking capabilities with Razorpay's robust billing & payment infrastructure. **What you'll achieve with this integration:** * Process payments securely through Razorpay's infrastructure * Automatically sync customers and invoices * Handle complex usage-based billing scenarios * Receive real-time updates via webhooks ## System Architecture The Flexprice-Razorpay integration follows a clear division of responsibilities, ensuring each system handles what it does best while maintaining seamless data flow between platforms. ### Division of Responsibilities **Flexprice specializes in:** * Usage-based charge calculation and metering * Complex billing logic and usage invoice generation * Plan configuration with usage components **Razorpay handles:** * Secure payment processing and compliance * Customer payment method storage * Transaction processing and failure handling * Payment link generation and checkout ## Prerequisites Before you begin, ensure the following: * You have an **active Flexprice account** * You have a **Razorpay account** (in test or live mode) * You possess **admin access** to both platforms * You have a **basic understanding of webhooks** (optional but helpful) *** ## Step 1: Set Up the Connection Follow the connection setup guide below to link Razorpay with Flexprice:\ 👉 [Razorpay Connection Setup Guide](/integrations/razorpay/connection-setup) ### Required Webhook Events ``` payment.captured payment.failed payment_link.paid payment_link.cancelled payment_link.expired ``` The `payment_link.*` events are required if you use [Flexprice Checkout Sessions](/docs/checkout/checkout-sessions). See [Razorpay Checkout Setup](/docs/checkout/razorpay-checkout) for the full configuration guide. *** ## Step 2: Sync Configuration Details ### Invoice Outbound Sync (Flexprice → Razorpay) * **What it does:** Sends invoices generated in Flexprice to Razorpay for payment processing * **When to enable:** You create invoices in Flexprice but want Razorpay to handle payment collection * **Benefits:** Unified payment experience and centralized transaction history #### Invoice Sync Timing Invoices are automatically synced to Razorpay when: * Invoice is finalized (status changes from `DRAFT` to `FINALIZED`) * Invoice outbound sync is enabled in the Razorpay connection * Customer is successfully synced to Razorpay #### Invoice Sync Process 1. **Invoice Finalization**: Invoice is finalized in Flexprice 2. **Customer Sync Check**: System ensures customer exists in Razorpay 3. **Invoice Creation**: Invoice is created in Razorpay with all line items 4. **Payment URL Generation**: Razorpay automatically generates payment URL 5. **Mapping Storage**: Flexprice stores the mapping between Flexprice and Razorpay invoices #### Invoice Sync Features * **Automatic Sync**: Invoices sync automatically when finalized * **Line Item Preservation**: All invoice line items are preserved in Razorpay * **Payment URL**: Razorpay generates payment URL automatically * **Duplicate Prevention**: System prevents duplicate invoice creation *** ## Step 3: Payment Flow Setup ### 3.1 Supported Payment Methods Flexprice supports multiple Razorpay payment flows: 1. **Payment Links** – For one-time payments via Razorpay Checkout 2. **Invoice Payments** – Payments made through synced invoices ### 3.2 Payment Collection Modes You can choose how customer payments are collected: | Mode | Description | | ----------------- | ----------------------------------------------------------------- | | **Payment Links** | Customers receive payment links to complete transactions manually | | **Invoice Sync** | Invoices are synced to Razorpay with auto-generated payment URLs | ### 3.3 Currency Minimums Razorpay requires minimum amounts in the smallest currency subunit: | Currency | Minimum amount (in smallest subunit) | Equivalent Minimum Value | | -------- | ------------------------------------ | ------------------------ | | INR | 100 | ₹1.00 | | USD | 1 | \$0.01 | **Important**: * Amounts are automatically converted to smallest currency units (paise for INR, cents for USD) * Ensure all payment amounts meet these minimums * Invoice amounts must also meet minimum requirements *** ## Step 4: Customer Synchronization ### On-Demand Customer Sync Customers are automatically synced to Razorpay when needed: * **Payment Link Creation**: Customer is synced before creating payment link * **Invoice Sync**: Customer is synced before syncing invoice **Note**: Customer sync is **one-way only** (Flexprice → Razorpay). Customers are only synced when payment interactions occur. ### Customer Sync Features * **Automatic Sync**: No manual intervention required * **Duplicate Prevention**: System prevents duplicate customer creation * **Metadata Linking**: Customer IDs are stored in metadata for quick lookup * **Email-Based Matching**: Uses email for customer matching For detailed information, see [Customer Synchronization Documentation](/integrations/razorpay/customer-sync). *** ## Step 5: Payment Links ### Creating Payment Links Payment links can be created for any invoice: **Endpoint:** `POST /api/v1/payments` **Request:** ```json theme={null} { "amount": 100, "currency": "inr", "destination_id": "inv_01K34HYZN03V6X8Q6V3T8X6G4F", "destination_type": "INVOICE", "payment_method_type": "PAYMENT_LINK", "process_payment": true, "payment_gateway": "razorpay", "success_url": "https://admin.flexprice.io/customer-management/invoices/inv_01K34HYZN03V6X8Q6V3T8X6G4F?page=1" } ``` ### Payment Link Features * **Auto-generated URLs**: Razorpay automatically generates short payment URLs * **Multiple Payment Methods**: Supports cards, UPI, netbanking, wallets * **Mobile Optimized**: Responsive checkout experience * **Real-time Updates**: Webhook-driven status updates For detailed information, see [Payment Links Documentation](/integrations/razorpay/payment-links). *** ## Step 6: Webhook Configuration ### Required Webhooks Configure these webhooks in your Razorpay dashboard: | Event | Purpose | | ------------------ | ------------------------------------ | | `payment.captured` | Track successful payment completions | | `payment.failed` | Handle payment failures and errors | ### Webhook URL Format ``` https://api.cloud.flexprice.io/v1/webhooks/razorpay/{tenant_id}/{environment_id} ``` ### Webhook Processing * **Signature Verification**: All webhooks are verified for security * **Idempotency**: Duplicate webhooks are handled gracefully * **Error Handling**: Errors are logged but don't fail webhook processing * **Automatic Reconciliation**: Payments are automatically reconciled with invoices *** ## Best Practices ### Currency Handling 1. **Minimum Amounts**: Always ensure amounts meet currency minimums 2. **Currency Conversion**: Amounts are automatically converted to smallest units 3. **Consistent Currency**: Use consistent currency across invoice and payment ### Invoice Sync 1. **Enable Sync**: Enable invoice outbound sync in connection settings 2. **Finalize Invoices**: Only finalized invoices are synced 3. **Customer Sync**: Ensure customers are synced before invoice sync ### Payment Links 1. **Amount Validation**: Validate amounts against invoice remaining balance 2. **Currency Matching**: Ensure payment currency matches invoice currency 3. **Callback URLs**: Provide callback URLs for better user experience ### Error Handling 1. **Monitor Webhooks**: Regularly check webhook delivery logs 2. **Review Logs**: Check application logs for sync errors 3. **Retry Failed Syncs**: Retry failed syncs manually if needed *** ## Troubleshooting ### Common Issues | Issue | Solution | | --------------------------- | -------------------------------------------------------------------- | | Payment link creation fails | Check customer sync status | | Invoice sync fails | Verify invoice is finalized and customer is synced | | Webhook not received | Verify webhook URL and events in Razorpay dashboard | | Amount below minimum | Ensure amount meets currency minimum (₹1.00 for INR, \$0.01 for USD) | ### Debug Steps 1. **Check Connection**: Verify Razorpay connection is active 2. **Check Customer Sync**: Ensure customer is synced to Razorpay 3. **Check Invoice Status**: Verify invoice is finalized 4. **Check Webhooks**: Verify webhook configuration in Razorpay 5. **Review Logs**: Check application logs for detailed error messages *** ## Support Need assistance? We're here to help. * **Flexprice Support:** [support@flexprice.com](mailto:support@flexprice.com) * **Integration Documentation:** [Flexprice Docs](https://docs.flexprice.com) * **Razorpay Documentation:** [Razorpay Docs](https://razorpay.com/docs) 💡 **Need help setting up?**\ Our team can assist with configuration, testing, and troubleshooting.\ Reach out to **Flexprice Support** to schedule a setup session. # Razorpay Payment Links & Invoice Sync Source: https://docs.flexprice.io/integrations/razorpay/payment-links Complete guide to creating payment links and handling the Razorpay checkout flow ## Overview Payment links in Flexprice provide a secure, hosted checkout experience powered by Razorpay. Customers click a payment URL and are redirected to Razorpay's secure checkout page to complete their payment. ### Key Features * **Secure Checkout**: Hosted by Razorpay with PCI compliance * **Multiple Payment Methods**: Cards, UPI, netbanking, wallets, and more * **Real-time Updates**: Webhook-driven status updates * **Invoice Integration**: Automatic invoice reconciliation * **Auto-generated Links**: Razorpay automatically generates payment link URLs ## Payment Link Lifecycle ```mermaid theme={null} graph TD A[Create Payment Request] --> B[Validate Invoice
& Customer] B --> C[Create Payment Record
INITIATED] C --> D[Generate Razorpay
Payment Link] D --> E{Link Created?} E -->|Success| F[Update Status
PENDING] E -->|Failure| G[Keep Status
INITIATED] F --> H[Return Payment URL] H --> I[Customer Clicks Link] I --> J[Razorpay Checkout] J --> K[Enter Payment Details] K --> L{Payment
Successful?} L -->|Yes| M[Webhook:
payment.captured] L -->|No| N[Webhook:
payment.failed] M --> O[Status: SUCCEEDED] N --> P[Status: FAILED] O --> Q[Reconcile Invoice] P --> R[Log Failure] ``` ## Payment Status Flow ### Status Transitions ``` INITIATED → PENDING → SUCCEEDED ↓ ↓ ↓ FAILED FAILED REFUNDED ``` ### Status Definitions | Status | Description | Triggers | | ------------ | ----------------------------------------------------- | ------------------------------------ | | `INITIATED` | Payment record created, Razorpay link not yet created | Initial payment creation | | `PENDING` | Razorpay payment link created successfully | Successful Razorpay API call | | `PROCESSING` | Payment is being processed | Temporary status during updates | | `SUCCEEDED` | Payment completed successfully | Razorpay webhook: `payment.captured` | | `FAILED` | Payment attempt failed | Razorpay webhook: `payment.failed` | | `REFUNDED` | Payment was refunded | Manual refund processing | ## Creating Payment Links ### Prerequisites 1. **Razorpay Connection**: Active Razorpay connection configured 2. **Customer Sync**: Customer must have Razorpay customer ID 3. **Invoice Status**: Invoice must be finalized and unpaid 4. **Valid Amount**: Payment amount must match invoice outstanding 5. **Currency Minimums**: Ensure amounts meet Razorpay minimums ### Currency Minimum Amounts Razorpay requires minimum amounts in the smallest currency subunit: | Currency | Minimum amount (in smallest subunit) | Equivalent Minimum Value | | -------- | ------------------------------------ | ------------------------ | | INR | 100 | ₹1.00 | | USD | 1 | \$0.01 | **Important**: Amounts are automatically converted to smallest currency units (paise for INR, cents for USD) when creating payment links. ### API Request **Endpoint:** `POST /api/v1/payments` **Headers:** ```http theme={null} Content-Type: application/json Authorization: Bearer your_api_key X-Environment-ID: your_environment_id ``` **Request Body:** ```json theme={null} { "amount": 100, "currency": "inr", "destination_id": "inv_01K34HYZN03V6X8Q6V3T8X6G4F", "destination_type": "INVOICE", "payment_method_type": "PAYMENT_LINK", "process_payment": true, "payment_gateway": "razorpay", "success_url": "https://admin.flexprice.io/customer-management/invoices/inv_01K34HYZN03V6X8Q6V3T8X6G4F?page=1" } ``` ### Response ```json theme={null} { "id": "pay_1234567890abcdef", "payment_status": "pending", "payment_url": "https://rzp.io/i/abc123", "amount": "100.00", "currency": "inr", "gateway_tracking_id": "plink_1234567890abcdef", "created_at": "2024-01-20T10:30:00Z", "metadata": { "razorpay_payment_link_id": "plink_1234567890abcdef" } } ``` **Note**: The `payment_url` is automatically generated by Razorpay and returned as a short URL (e.g., `https://rzp.io/i/abc123`). ### Creating Payment Links via Dashboard You can also create payment links directly from the Flexprice dashboard: Create Payment Link ## Payment Link Configuration ### Required Fields | Field | Type | Required | Description | | --------------------- | ------- | -------- | ------------------------------------- | | `destination_type` | string | ✅ | Must be `"INVOICE"` | | `destination_id` | string | ✅ | Valid invoice ID | | `payment_method_type` | string | ✅ | Must be `"PAYMENT_LINK"` | | `payment_gateway` | string | ✅ | Must be `"razorpay"` | | `amount` | string | ✅ | Payment amount (must match invoice) | | `currency` | string | ✅ | Valid currency code (INR, USD, etc.) | | `process_payment` | boolean | ✅ | Set to `true` to create Razorpay link | | `success_url` | string | ❌ | Callback URL after payment (optional) | ## Razorpay Payment Link Configuration ### Default Payment Link Settings When creating payment links, Flexprice automatically configures Razorpay with: * **Payment Methods**: Cards, UPI, netbanking, wallets, and more * **Currency**: Matches the payment currency * **Amount**: Exact payment amount (converted to smallest unit) * **Customer**: Pre-filled with customer data * **Description**: Invoice details and line items * **Notifications**: Email and SMS notifications enabled * **Reminders**: Payment reminders enabled ### Callback URL Razorpay uses a single `callback_url` for both successful and cancelled payments: * **Success**: Customer is redirected to `callback_url` with payment details * **Cancel**: Customer is redirected to `callback_url` with cancellation status * **Method**: Only GET method is supported for payment links ## Webhook Processing ### Payment Completion Webhook When a payment is completed, Razorpay sends a `payment.captured` webhook: ```json theme={null} { "event": "payment.captured", "payload": { "payment": { "entity": { "id": "pay_1234567890abcdef", "amount": 10000, "currency": "INR", "status": "captured", "invoice_id": "inv_razorpay123", "notes": { "flexprice_payment_id": "pay_1234567890abcdef" } } } } } ``` ### Webhook Processing Flow 1. **Receive Webhook**: Flexprice receives the webhook from Razorpay 2. **Verify Signature**: Validate webhook signature for security 3. **Find Payment**: Locate payment record using payment ID from notes 4. **Update Status**: Change payment status to `SUCCEEDED` 5. **Reconcile Invoice**: Update invoice payment status and amounts 6. **Trigger Events**: Send payment completion events ## Error Handling ### Common Payment Errors | Error | Cause | Resolution | | -------------------------------- | ---------------------- | ----------------------------------------------------------- | | "Customer not found in Razorpay" | Customer not synced | Sync customer to Razorpay first | | "Invalid invoice amount" | Amount mismatch | Ensure amount matches invoice | | "Invoice already paid" | Invoice status issue | Check invoice payment status | | "Amount below minimum" | Below currency minimum | Ensure amount meets minimum (₹1.00 for INR, \$0.01 for USD) | | "Razorpay link creation failed" | API error | Check Razorpay connection | ### Payment Failure Handling When payments fail: 1. **Webhook Received**: Razorpay sends `payment.failed` webhook 2. **Status Updated**: Payment status changed to `FAILED` 3. **Error Logged**: Failure reason recorded 4. **Customer Notified**: Optional failure notification 5. **Retry Available**: Customer can create new payment link ## Testing Payment Links ### Test Environment Setup 1. **Use Test Keys**: Configure Razorpay test mode 2. **Test Cards**: Use Razorpay's test card numbers 3. **Test Webhooks**: Use ngrok for local webhook testing ### Test Card Numbers #### Test Cards for Indian Payments | Card Network | Card Number | CVV | Expiry Date | | ------------ | --------------------- | ---------- | --------------- | | Mastercard | `2305 3242 5784 8228` | Random CVV | Any future date | | Visa | `4386 2894 0766 0153` | Random CVV | Any future date | #### Test Cards for International Payments | Card Network | Card Number | CVV | Expiry Date | | ------------ | --------------------- | ---------- | --------------- | | Mastercard | `5421 1393 0609 0628` | Random CVV | Any future date | | Mastercard | `5105 1051 0510 5100` | Random CVV | Any future date | | Mastercard | `5104 0600 0000 0008` | Random CVV | Any future date | | Visa | `4012 8888 8888 1881` | Random CVV | Any future date | ### Test UPI IDs | UPI ID | Description | | ------------------ | ------------------ | | `success@razorpay` | Successful payment | | `failure@razorpay` | Failed payment | #### To use the test UPI ID details: 1. At the Checkout, select **UPI** as the payment method 2. Enter the UPI ID 3. **Test payment success flow** using `success@razorpay` 4. **Test payment failure flow** using `failure@razorpay` **Watch Out!** In test mode, payment cancellation will result in a successful payment. Use the live mode to test payment cancellation on UPI. ### Testing Flow 1. **Create Test Payment**: Use test invoice and customer 2. **Generate Payment Link**: Create payment with test data 3. **Complete Payment**: * Use test card numbers listed above in Razorpay checkout * Or use test UPI ID `success@razorpay` for UPI payments 4. **Verify Webhook**: Check webhook processing 5. **Confirm Status**: Verify payment and invoice updates ## Invoice Integration ### Payment Links from Synced Invoices When an invoice is synced to Razorpay: 1. **Invoice Sync**: Invoice is created in Razorpay with payment URL 2. **Payment Link Reuse**: If payment link is requested for synced invoice, the existing Razorpay invoice payment URL is returned 3. **Automatic Association**: Payments made through the URL are automatically associated with the invoice ### Invoice Sync Timing Invoices are automatically synced to Razorpay when: * Invoice is finalized (status changes from `DRAFT` to `FINALIZED`) * Invoice outbound sync is enabled in the Razorpay connection * Customer is successfully synced to Razorpay ## Security Considerations ### Payment Link Security * **HTTPS Required**: All payment links use HTTPS * **Link Expiry**: Links expire based on Razorpay settings * **One-time Use**: Each link can only be used once * **Signature Verification**: All webhooks verified ### Data Protection * **PCI Compliance**: Razorpay handles sensitive payment data * **No Card Storage**: Card details never stored in Flexprice * **Encrypted Storage**: All payment data encrypted at rest * **Access Controls**: Role-based access to payment data ## Webhook Endpoints * `POST /api/v1/webhooks/razorpay/{tenant_id}/{environment_id}` - Razorpay webhook handler For complete API documentation, see the [API Reference](/api-reference/payments/create-payment). # Card Payment Checkout Flow Source: https://docs.flexprice.io/integrations/stripe/card-payment Complete guide to processing card payments using saved payment methods ## Overview Card payments in Flexprice allow customers to pay using their previously saved payment methods. This flow is designed for returning customers who have already saved their card details through a payment link with `save_card_and_make_default: true`. ### Key Features * **Instant Payments**: No redirect to external checkout pages * **Saved Payment Methods**: Uses customer's default payment method * **Off-Session Processing**: Payments processed without customer interaction * **Automatic Reconciliation**: Invoice updates happen automatically * **Secure Processing**: All card data handled by Stripe ## Card Payment Prerequisites ### Customer Requirements * **Existing Customer**: Customer must exist in both Flexprice and Stripe * **Default Payment Method**: Customer must have a default payment method set in Stripe * **Previous Payment Link**: Default payment method created via payment link with `save_card_and_make_default: true` ### System Requirements * **Stripe Integration**: Active Stripe connection configured * **Valid Invoice**: Invoice must be finalized and have outstanding amount * **Matching Currency**: Payment currency must match invoice currency ## Payment Status Flow ### Status Transitions for Card Payments ``` INITIATED → PROCESSING → SUCCEEDED ↓ ↓ ↓ FAILED FAILED REFUNDED ``` ### Status Definitions | Status | Description | Triggers | | ------------ | ------------------------------------------ | ------------------------------------------ | | `INITIATED` | Payment record created, validation pending | Initial payment creation | | `PROCESSING` | Payment intent created and being processed | Stripe payment intent confirmation | | `SUCCEEDED` | Payment completed successfully | Successful charge confirmation | | `FAILED` | Payment attempt failed | Stripe payment failure or validation error | | `REFUNDED` | Payment was refunded | Manual refund processing | ## Creating Card Payments ### API Request **Endpoint:** `POST /api/v1/payments` **Headers:** ```http theme={null} Content-Type: application/json Authorization: Bearer your_api_key X-Environment-ID: your_environment_id ``` **Request Body:** ```json theme={null} { "amount": 150.00, "currency": "usd", "destination_id": "inv_01K34HYZN03V6X8Q6V3T8X6G4F", "destination_type": "INVOICE", "payment_method_type": "CARD", "process_payment": true, "payment_gateway": "stripe" } ``` ### Response **Success Response:** ```json theme={null} { "id": "pay_1234567890abcdef", "payment_status": "succeeded", "amount": "150.00", "currency": "usd", "payment_method_id": "pm_1234567890abcdef", "gateway_tracking_id": "pi_1234567890abcdef", "created_at": "2024-01-20T10:30:00Z", "metadata": { "stripe_payment_intent_id": "pi_1234567890abcdef", "card_last4": "4242", "card_brand": "visa" } } ``` **Error Response:** ```json theme={null} { "error": { "code": "no_default_payment_method", "message": "Customer must have a default payment method to make card payments. Please make a payment using payment link with save_card_and_make_default=true first.", "details": { "customer_id": "cus_1234567890abcdef", "payment_id": "pay_1234567890abcdef" } } } ``` ## Card Payment Configuration ### Required Fields | Field | Type | Required | Description | | --------------------- | ------- | -------- | ------------------------------------ | | `destination_type` | string | ✅ | Must be `"INVOICE"` | | `destination_id` | string | ✅ | Valid invoice ID | | `payment_method_type` | string | ✅ | Must be `"CARD"` | | `payment_gateway` | string | ✅ | Must be `"stripe"` | | `amount` | decimal | ✅ | Payment amount (must match invoice) | | `currency` | string | ✅ | Valid currency code | | `process_payment` | boolean | ✅ | Set to `true` to process immediately | ### Optional Fields | Field | Type | Required | Description | | ------------------- | ------ | -------- | -------------------------------------------------- | | `payment_method_id` | string | ❌ | Specific payment method ID (uses default if empty) | | `metadata` | object | ❌ | Additional payment metadata | ### Payment Method Selection When creating a card payment, you can specify which payment method to use: * **No `payment_method_id` provided**: Uses the customer's default payment method * **`payment_method_id` provided**: Uses the specified payment method for this payment **Example with specific payment method:** ```json theme={null} { "amount": 150.00, "currency": "usd", "destination_id": "inv_01K34HYZN03V6X8Q6V3T8X6G4F", "destination_type": "INVOICE", "payment_method_type": "CARD", "payment_method_id": "pm_1234567890abcdef", "process_payment": true, "payment_gateway": "stripe" } ``` **cURL Example:** ```bash theme={null} curl -X POST https://api.cloud.flexprice.io/v1/payments \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your_api_key" \ -H "X-Environment-ID: your_environment_id" \ -d '{ "amount": 150.00, "currency": "usd", "destination_id": "inv_01K34HYZN03V6X8Q6V3T8X6G4F", "destination_type": "INVOICE", "payment_method_type": "CARD", "payment_method_id": "pm_1234567890abcdef", "process_payment": true, "payment_gateway": "stripe" }' ``` ## Payment Processing Flow ### Payment Validation Before processing, the system validates: * **Invoice Status**: Must be finalized and have outstanding amount * **Customer Existence**: Customer must exist in both systems * **Amount Matching**: Payment amount must match invoice outstanding * **Currency Matching**: Payment and invoice currencies must match ### Default Payment Method Retrieval ```mermaid theme={null} graph TD A[Check Payment Method ID] --> B{ID Provided?} B -->|Yes| C[Use Provided ID] B -->|No| D[Get Customer Default Payment Method] D --> E{Default Exists?} E -->|Yes| F[Use Default Payment Method] E -->|No| G[Return Error] C --> H[Process Payment] F --> H[Process Payment] ``` ### Stripe Payment Intent Creation The system creates a Stripe Payment Intent with: * **Amount**: Exact payment amount in smallest currency unit * **Currency**: Payment currency * **Customer**: Stripe customer ID * **Payment Method**: Default payment method ID * **Confirmation**: Immediate confirmation (`confirm: true`) * **Off Session**: Set to `true` for saved payment methods ### Payment Confirmation Payment intents are automatically confirmed with the following parameters: ```json theme={null} { "amount": 15000, "currency": "usd", "customer": "cus_stripe123abc", "payment_method": "pm_1234567890abcdef", "off_session": true, "confirm": true } ``` ## Error Handling ### Common Card Payment Errors | Error Code | Cause | Resolution | | --------------------------- | -------------------------------------- | ----------------------------------------------------------- | | `no_default_payment_method` | Customer has no default payment method | Create payment link with `save_card_and_make_default: true` | | `card_declined` | Card was declined by issuer | Customer should contact their bank | | `insufficient_funds` | Insufficient funds on card | Customer should use different payment method | | `authentication_required` | 3D Secure authentication needed | Use payment link flow for authentication | | `expired_card` | Card has expired | Customer needs to update payment method | ### Error Response Format > **Note:** All card payment errors follow this standardized format for consistent error handling. ```json theme={null} { "error": { "code": "error_code", "message": "Human-readable error message", "details": { "customer_id": "cus_1234567890abcdef", "payment_id": "pay_1234567890abcdef", "stripe_error_code": "card_declined" } } } ``` ### Recovery Flow 1. **Immediate Failure**: Payment status set to `FAILED` 2. **Error Logging**: Detailed error information recorded 3. **Customer Notification**: Optional failure notification sent 4. **Alternative Options**: * Retry with payment link flow * Update payment method via payment link with `save_card_and_make_default: true` * Use different payment method ## Security Considerations ### Payment Security * **Off-Session Payments**: All card payments are off-session using saved methods * **PCI Compliance**: No card data stored in Flexprice systems * **Stripe Security**: All sensitive operations handled by Stripe * **Authentication**: Strong Customer Authentication (SCA) compliance ### Data Protection * **Encrypted Storage**: All payment data encrypted at rest * **Access Controls**: Role-based access to payment information * **Audit Logging**: All payment operations logged for compliance * **Data Retention**: Payment data retained per compliance requirements ## Testing Card Payments ### Test Environment Setup 1. **Stripe Test Mode**: Use Stripe test keys 2. **Test Customer**: Create customer with saved payment method 3. **Test Payment Link**: First create payment link with `save_card_and_make_default: true` 4. **Test Card Payment**: Then test card payment flow ### Test Scenarios | Scenario | Test Card | Expected Result | | ----------------------- | ------------------ | ------------------------------------- | | Successful Payment | `4242424242424242` | Payment succeeds | | Declined Card | `4000000000000002` | Payment fails with decline | | Insufficient Funds | `4000000000009995` | Payment fails with insufficient funds | | Authentication Required | `4000002760003184` | Payment fails, requires payment link | ## API Reference For complete API documentation, see the [API Reference](/api-reference/payments/create-payment). # Stripe Connection Setup Source: https://docs.flexprice.io/integrations/stripe/connection-setup Complete guide to setting up and configuring Stripe connections in Flexprice ## Overview A Stripe connection in Flexprice stores encrypted credentials that allow the system to interact with your Stripe account for: * Creating customers in Stripe * Processing payment links via Stripe Checkout * Receiving webhook notifications from Stripe * Synchronizing customer data between systems ## Video Walkthrough Watch this step-by-step guide to setting up your Stripe connection: