# Anrok Source: https://docs.withorb.com/anrok # Overview ## **Provider-specific behaviors** * The transaction in Anrok includes the amount per line item and the specified tax code used for computation. Line item-level minimums or discounts are synced as separate (negative) line items. ## **Geographic coverage** US sales tax + global VAT/GST across 100+ countries. # Setup ## **Prerequisites** 1. Read the [Anrok Getting Started Guide](https://help-center.anrok.com/hc/en-us/categories/4410165176339-Getting-Started-Guide). 2. Anrok account: ensure you have an Anrok account, and ideally an Anrok sandbox you can connect to your Orb test mode account. You can get a free Anrok sandbox by contacting the Anrok team. 3. Physical nexus: ensure you're correctly recording physical nexus, via the Jurisdictions tab and the “Manage Physical Nexus” button. 4. Products in Anrok: create products in Anrok that associate a tax configuration to each product you sell in Orb. You'll need these product IDs when configuring the integration in Orb. 5. Customer exemptions in Anrok: upload valid exemption certificates for customers, using the Orb customer ID so Anrok applies them correctly when calculating tax. ## **Connecting your account** 1. Navigate to **Settings > Taxes** in the Orb dashboard. 2. Select “Connect to Anrok.” 3. Create an Anrok API key. 1. In your Anrok account, go to Settings > Developers > Manage API Keys > New key. 4. In Orb, enter your Anrok API key and API key secret. The sellerId is expected as part of the key, in the format /. Invalid credentials will error at this stage. ## **Setting up tax codes** **Anrok tax code configuration** Because different jurisdictions may tax items at different rates, register the type of product in your Anrok instance under the Product IDs section. Create a unique Anrok product for each type of product you sell — one Anrok product can map to multiple external product IDs of the same type. **Orb tax code configuration** Orb supports tax code configuration per Item. Items represent a sellable product or service in Orb and ensure invoice line items map correctly to your tax/accounting services. To configure, navigate to **Settings > Items > Item name > edit**, and fill in the External item ID with the ID of the corresponding Anrok product. # **Sample request and response payload** *The example below is illustrative of the shape Orb sends to and receives from Anrok today, based on Orb's tax provider processors and test fixtures. Exact field names and structure can change as Anrok versions its API — confirm against current behavior before relying on it for integration work.* ### ***Request (Orb → Anrok)*** ```text theme={null} { "customerId": "", "customerName": "", "currencyCode": "USD", "customerAddress": { "line1": "345 California Floor 6", "city": "San Francisco", "country": "US", "postalCode": "94104", "region": "CA" }, "accountingDate": "2022-09-01", "lineItems": [ { "amount": 5000, "productExternalId": "custom-tax-code-price-id", "id": "bjur5ox8eqwHXgNa" } ] } ``` ### ***Response (Anrok → Orb)*** ```text theme={null} { "taxAmountToCollect": 825, "lineItems": [ { "id": "line_item_1", "taxAmountToCollect": 825, "preTaxAmount": 10000, "jurises": [ { "name": "US State", "notTaxedReason": null, "taxes": [ { "taxName": "State Tax", "taxRate": 0.0825 } ] } ] } ] } ``` ## **Reporting tax for filing** Once an invoice is issued or finalized (not when it's paid), Orb makes a second call to Anrok to report the transaction for filing: POST /v1/seller/transactions/createOrUpdate. The reporting payload has the same shape as the calculation payload, with one addition — an id field set to the Orb invoice ID. ```text theme={null} { "id": "", "customerId": "", "customerName": "", "currencyCode": "USD", "customerAddress": { "line1": "345 California Floor 6", "city": "San Francisco", "country": "US", "postalCode": "94104", "region": "CA" }, "accountingDate": "2022-09-01", "lineItems": [ { "amount": 5000, "productExternalId": "custom-tax-code-price-id", "id": "bjur5ox8eqwHXgNa" } ] } ``` # Create customer alert Source: https://docs.withorb.com/api-reference/alert/create-customer-alert /api-reference/orb-openapi.json post /alerts/customer_id/{customer_id} This endpoint creates a new alert to monitor a customer's credit balance. There are three types of alerts that can be scoped to customers: `credit_balance_depleted`, `credit_balance_dropped`, and `credit_balance_recovered`. Customers can have a maximum of one of each type of alert per [credit balance currency](/product-catalog/prepurchase). `credit_balance_dropped` alerts require a list of thresholds to be provided while `credit_balance_depleted` and `credit_balance_recovered` alerts do not require thresholds. # Create customer alert by external ID Source: https://docs.withorb.com/api-reference/alert/create-customer-alert-by-external-id /api-reference/orb-openapi.json post /alerts/external_customer_id/{external_customer_id} This endpoint creates a new alert to monitor a customer's credit balance. There are three types of alerts that can be scoped to customers: `credit_balance_depleted`, `credit_balance_dropped`, and `credit_balance_recovered`. Customers can have a maximum of one of each type of alert per [credit balance currency](/product-catalog/prepurchase). `credit_balance_dropped` alerts require a list of thresholds to be provided while `credit_balance_depleted` and `credit_balance_recovered` alerts do not require thresholds. # Create plan alert Source: https://docs.withorb.com/api-reference/alert/create-plan-alert /api-reference/orb-openapi.json post /alerts/plan_id/{plan_id} This endpoint is used to create alerts at the plan level. Plan level alerts are automatically propagated to all subscriptions associated with the plan. These alerts are scoped to a specific plan version; if no version is specified, the active plan version is used. Plan level alerts can be of three types: `usage_exceeded`, `cost_exceeded`, or `spend_exceeded`. A `usage_exceeded` alert is scoped to a particular metric and is triggered when the usage of that metric exceeds predefined thresholds during the current billing cycle. A `cost_exceeded` alert is triggered when the total amount due during the current billing cycle surpasses predefined thresholds. `cost_exceeded` alerts do not include burndown of pre-purchase credits. A `spend_exceeded` alert is triggered when the rated spend (the pricing subtotal, before invoice-level adjustments and credits) denominated in the alert's currency exceeds predefined thresholds during the current billing cycle; `price_filters` can scope which prices contribute. Each plan can have one `cost_exceeded` alert and one `usage_exceeded` alert per metric that is a part of the plan. # Create subscription alert Source: https://docs.withorb.com/api-reference/alert/create-subscription-alert /api-reference/orb-openapi.json post /alerts/subscription_id/{subscription_id} This endpoint is used to create alerts at the subscription level. Subscription level alerts can be one of three types: `usage_exceeded`, `cost_exceeded`, or `spend_exceeded`. A `usage_exceeded` alert is scoped to a particular metric and is triggered when the usage of that metric exceeds predefined thresholds during the current billing cycle. A `cost_exceeded` alert is triggered when the total amount due during the current billing cycle surpasses predefined thresholds. `cost_exceeded` alerts do not include burndown of pre-purchase credits. A `spend_exceeded` alert is triggered when the rated spend (the pricing subtotal, before invoice-level adjustments and credits) denominated in the alert's currency exceeds predefined thresholds during the current billing cycle; `price_filters` can scope which prices contribute. Each subscription can have one `cost_exceeded` alert and one `usage_exceeded` alert per metric that is a part of the subscription. # Delete alert Source: https://docs.withorb.com/api-reference/alert/delete-alert /api-reference/orb-openapi.json delete /alerts/{alert_configuration_id} This endpoint deletes a subscription or customer-scoped alert. The alert is soft-deleted: it stops firing immediately and no longer appears in fetch or list responses, while the underlying record is retained internally for audit. Plan-level alerts cannot be deleted via the API — disable them instead (`POST /v1/alerts/{alert_configuration_id}/disable`). Their removal would need to be unwound from every subscription the alert was propagated to, which isn't supported yet. # Disable alert by ID Source: https://docs.withorb.com/api-reference/alert/disable-alert-by-id /api-reference/orb-openapi.json post /alerts/{alert_configuration_id}/disable This endpoint allows you to disable an alert. To disable a plan-level alert for a specific subscription, you must include the `subscription_id`. The `subscription_id` is not required for customer or subscription level alerts. # Enable alert by ID Source: https://docs.withorb.com/api-reference/alert/enable-alert-by-id /api-reference/orb-openapi.json post /alerts/{alert_configuration_id}/enable This endpoint allows you to enable an alert. To enable a plan-level alert for a specific subscription, you must include the `subscription_id`. The `subscription_id` is not required for customer or subscription level alerts. # Fetch alert Source: https://docs.withorb.com/api-reference/alert/fetch-alert /api-reference/orb-openapi.json get /alerts/{alert_id} This endpoint retrieves an alert by its ID. # List alerts Source: https://docs.withorb.com/api-reference/alert/list-alerts /api-reference/orb-openapi.json get /alerts This endpoint returns a list of alerts within Orb. The request must specify one of `customer_id`, `external_customer_id`, or `subscription_id`. If querying by subscription_id, the endpoint will return the subscription level alerts as well as the plan level alerts associated with the subscription. The list of alerts is ordered starting from the most recently created alert. This endpoint follows Orb's [standardized pagination format](/api-reference/pagination). # Update alert Source: https://docs.withorb.com/api-reference/alert/update-alert /api-reference/orb-openapi.json put /alerts/{alert_configuration_id} This endpoint updates the thresholds of an alert. On cost alerts it also updates `price_filters`, and on subscription-scoped grouped cost alerts `threshold_overrides`; omitting either leaves it unchanged, and an empty list clears it. # Check availability Source: https://docs.withorb.com/api-reference/availability/check-availability /api-reference/orb-openapi.json get /ping This endpoint allows you to test your connection to the Orb API and check the validity of your API key, passed in the Authorization header. This is particularly useful for checking that your environment is set up properly, and is a great choice for connectors and integrations. This API does not have any side-effects or return any Orb resources. # Cached usage responses Source: https://docs.withorb.com/api-reference/cached-responses Orb features several API endpoints to query usage data for a given Subscription or Customer. It's common to use these in order to power dashboards or views in your own application, allowing your end users to see their current or historical usage. Orb's data architecture is designed to give you usage results performantly, and the default implementations of usage fetching endpoints prioritize data freshness over latency. When it's more important to fetch data *quickly* which may not capture the latest usage, Orb provides the ability for you to query cached results. By default, Orb does not recompute data when it's known to be fresh, and also provides an explicit opt-in feature to use cached and potentially stale data. We recommend using this in cases where you plan to render a chart or preview on page load, allowing you to build an interaction where the latest data is loaded asynchronously if necessary. The performance difference between fetching cached and live data depends on the number and cost of queries against the events datastore that would otherwise be required. In most cases, you should expect at a 1.5-2x speedup in latency. ## Supported endpoints The currently supported endpoints are: * [`/v1/subscriptions//usage`](/api-reference/subscription/fetch-subscription-usage) * [`/v1/subscriptions//costs`](/api-reference/subscription/fetch-subscription-costs) * [`/v1/customers//costs`](/api-reference/customer/fetch-customer-costs) * [`/v1/customers/external_customer_id//costs`](/api-reference/customer/fetch-customer-costs-by-external-id) * [`/v1/customers//credits`](/api-reference/credit/fetch-customer-credit-balance) * [`/v1/customers/external_customer_id//credits`](/api-reference/credit/fetch-customer-credit-balance-by-external-customer-id) * [`/v1/customers//credits/ledger`](/api-reference/credit/fetch-customer-credits-ledger) * [`/v1/customers/external_customer_id//credits/ledger`](/api-reference/credit/fetch-customer-credits-ledger-by-external-id) * [`/v1/invoices/upcoming`](/api-reference/invoice/fetch-upcoming-invoice) * [`/v1/invoices/`](/api-reference/invoice/fetch-invoice) Performance benefits are only expected on invoices with `status = "draft"`, since issued invoices already have frozen data. ## Header semantics Orb currently supports two approaches for requesting cached data. ### Always fetching cached data In order to opt-in to cached data, explicitly pass the following case-sensitive header: | Key | Value | | ------------------- | ------- | | `Orb-Cache-Control` | `cache` | ### Fetch cached data conditional on a staleness threshold In order to conditionally opt-in to cached data based on a maximum staleness age, pass the following headers: | Key | Value | | --------------------------- | ------------------------------------- | | `Orb-Cache-Control` | `cache` | | `Orb-Cache-Max-Age-Seconds` | ` (int)` | The staleness of the cache is determined based on Orb's invalidation based architecture, which tracks any potentially material changes to an Orb [customer](/core-concepts#customer). Material changes include (but are not limited to) events ingested, subscription lifecycle changes, and prepurchase balance adjustments. Upon receiving an invalidating event, Orb marks a customer as invalid and schedules processing in the near future to rehydrate the cache. ### Response headers On `HTTP 200` responses when the `Orb-Cache-Control` header is passed with or without `Orb-Cache-Max-Age-Seconds`, the response will return the following header: | Key | Value | | ---------------------- | -------------------------------------------------------------- | | `Orb-Cache-Updated-At` | ISO timestamp string (e.g. `2024-02-14T06:48:26.366184+00:00`) | In "always fetch cached" scenarios, we recommend using the `Orb-Cache-Updated-At` value to determine if a follow-up request is necessary. If a specific result is completely up to date (based on internal event ingestion invalidation markers), Orb will set this value to the current time. # Archive coupon Source: https://docs.withorb.com/api-reference/coupon/archive-coupon /api-reference/orb-openapi.json post /coupons/{coupon_id}/archive This endpoint allows a coupon to be archived. Archived coupons can no longer be redeemed, and will be hidden from lists of active coupons. Additionally, once a coupon is archived, its redemption code can be reused for a different coupon. # Create coupon Source: https://docs.withorb.com/api-reference/coupon/create-coupon /api-reference/orb-openapi.json post /coupons This endpoint allows the creation of coupons, which can then be redeemed at subscription creation or plan change. # Fetch coupon Source: https://docs.withorb.com/api-reference/coupon/fetch-coupon /api-reference/orb-openapi.json get /coupons/{coupon_id} This endpoint retrieves a coupon by its ID. To fetch coupons by their redemption code, use the [List coupons](list-coupons) endpoint with the redemption_code parameter. # List coupon subscriptions Source: https://docs.withorb.com/api-reference/coupon/list-coupon-subscriptions /api-reference/orb-openapi.json get /coupons/{coupon_id}/subscriptions This endpoint returns a list of all subscriptions that have redeemed a given coupon as a [paginated](/api-reference/pagination) list, ordered starting from the most recently created subscription. For a full discussion of the subscription resource, see [Subscription](/core-concepts#subscription). # List coupons Source: https://docs.withorb.com/api-reference/coupon/list-coupons /api-reference/orb-openapi.json get /coupons This endpoint returns a list of all coupons for an account in a list format. The list of coupons is ordered starting from the most recently created coupon. The response also includes `pagination_metadata`, which lets the caller retrieve the next page of results if they exist. # Create credit note Source: https://docs.withorb.com/api-reference/credit-note/create-credit-note /api-reference/orb-openapi.json post /credit_notes This endpoint is used to create a single [`Credit Note`](/invoicing/credit-notes). The credit note service period configuration supports two explicit modes: 1. Global service periods: Specify start_date and end_date at the credit note level. These dates will be applied to all line items uniformly. 2. Individual service periods: Specify start_date and end_date for each line item. When using this mode, ALL line items must have individual periods specified. 3. Default behavior: If no service periods are specified (neither global nor individual), the original invoice line item service periods will be used. Note: Mixing global and individual service periods in the same request is not allowed to prevent confusion. Service period dates are normalized to the start of the day in the customer's timezone to ensure consistent handling across different timezones. Date Format: Use start_date and end_date with format "YYYY-MM-DD" (e.g., "2023-09-22") to match other Orb APIs like /v1/invoice_line_items. Note: Both start_date and end_date are inclusive - the service period will cover both the start date and end date completely (from start of start_date to end of end_date). # Fetch credit note Source: https://docs.withorb.com/api-reference/credit-note/fetch-credit-note /api-reference/orb-openapi.json get /credit_notes/{credit_note_id} This endpoint is used to fetch a single [`Credit Note`](/invoicing/credit-notes) given an identifier. # List credit notes Source: https://docs.withorb.com/api-reference/credit-note/list-credit-notes /api-reference/orb-openapi.json get /credit_notes Get a paginated list of CreditNotes. Users can also filter by customer_id, subscription_id, or external_customer_id. The credit notes will be returned in reverse chronological order by `creation_time`. # Create ledger entry Source: https://docs.withorb.com/api-reference/credit/create-ledger-entry /api-reference/orb-openapi.json post /customers/{customer_id}/credits/ledger_entry This endpoint allows you to create a new ledger entry for a specified customer's balance. This can be used to increment balance, deduct credits, and change the expiry date of existing credits. ## Effects of adding a ledger entry 1. After calling this endpoint, [Fetch Credit Balance](fetch-customer-credits) will return a credit block that represents the changes (i.e. balance changes or transfers). 2. A ledger entry will be added to the credits ledger for this customer, and therefore returned in the [View Credits Ledger](fetch-customer-credits-ledger) response as well as serialized in the response to this request. In the case of deductions without a specified block, multiple ledger entries may be created if the deduction spans credit blocks. 3. If `invoice_settings` is specified, an invoice will be created that reflects the cost of the credits (based on `amount` and `per_unit_cost_basis`). ## Adding credits Adding credits is done by creating an entry of type `increment`. This requires the caller to specify a number of credits as well as an optional expiry date in `YYYY-MM-DD` format. Orb also recommends specifying a description to assist with auditing. When adding credits, the caller can also specify a cost basis per-credit, to indicate how much in USD a customer paid for a single credit in a block. This can later be used for revenue recognition. The following snippet illustrates a sample request body to increment credits which will expire in January of 2022. ```json { "entry_type": "increment", "amount": 100, "expiry_date": "2022-12-28", "per_unit_cost_basis": "0.20", "description": "Purchased 100 credits" } ``` Note that an `increment` entry always creates a new credit block (defined by the optional `effective_date` and `expiry_date`); it never modifies an existing block. ### Invoicing for credits By default, Orb manipulates the credit ledger but does not charge for credits. However, if you pass `invoice_settings` in the body of this request, Orb will also generate a one-off invoice for the customer for the credits pre-purchase. Note that you _must_ provide the `per_unit_cost_basis`, since the total charges on the invoice are calculated by multiplying the cost basis with the number of credit units added. If you invoice or handle payment of credits outside of Orb (i.e. marketplace customers), set `mark_as_paid` in the `invoice_settings` to `true` to prevent duplicate invoicing effects. * if `per_unit_cost_basis` is greater than zero, an invoice will be generated and `invoice_settings` must be included * if `invoice_settings` is passed, one of either `custom_due_date` or `net_terms` is required to determine the due date ## Deducting Credits Orb allows you to deduct credits from a customer by creating an entry of type `decrement`. A `decrement` entry records credits as usage and immediately recognizes revenue at the block's `per_unit_cost_basis`. For most credit removals, use `void` (no revenue impact) or `expiration_change` (revenue recognized on expiration) instead. Only use `decrement` when credits were genuinely consumed outside of normal event ingestion. Orb matches the algorithm for automatic deductions for determining which credit blocks to decrement from. In the case that the deduction leads to multiple ledger entries, the response from this endpoint will be the final deduction. Orb also optionally allows specifying a description to assist with auditing. The following snippet illustrates a sample request body to decrement credits. ```json { "entry_type": "decrement", "amount": 20, "description": "Removing excess credits" } ``` ## Changing credits expiry If you'd like to change when existing credits expire, you should create a ledger entry of type `expiration_change`. For this entry, the required parameter `expiry_date` identifies the _originating_ block, and the required parameter `target_expiry_date` identifies when the transferred credits should now expire. A new credit block will be created with expiry date `target_expiry_date`, with the same cost basis data as the original credit block, if present. Note that the balance of the block with the given `expiry_date` must be at least equal to the desired transfer amount determined by the `amount` parameter. The following snippet illustrates a sample request body to extend the expiration date of credits by one year: ```json { "entry_type": "expiration_change", "amount": 10, "expiry_date": "2022-12-28", "block_id": "UiUhFWeLHPrBY4Ad", "target_expiry_date": "2023-12-28", "description": "Extending credit validity" } ``` ## Voiding credits If you'd like to void a credit block, create a ledger entry of type `void`. For this entry, `block_id` is required to identify the block, and `amount` indicates how many credits to void, up to the block's initial balance. Pass in a `void_reason` of `refund` if the void is due to a refund. ## Amendment If you'd like to undo a decrement on a credit block, create a ledger entry of type `amendment`. For this entry, `block_id` is required to identify the block that was originally decremented from, and `amount` indicates how many credits to return to the customer, up to the block's initial balance. # Create ledger entry by external ID Source: https://docs.withorb.com/api-reference/credit/create-ledger-entry-by-external-id /api-reference/orb-openapi.json post /customers/external_customer_id/{external_customer_id}/credits/ledger_entry This endpoint allows you to create a new ledger entry for a specified customer's balance. This can be used to increment balance, deduct credits, and change the expiry date of existing credits. ## Effects of adding a ledger entry 1. After calling this endpoint, [Fetch Credit Balance](fetch-customer-credits) will return a credit block that represents the changes (i.e. balance changes or transfers). 2. A ledger entry will be added to the credits ledger for this customer, and therefore returned in the [View Credits Ledger](fetch-customer-credits-ledger) response as well as serialized in the response to this request. In the case of deductions without a specified block, multiple ledger entries may be created if the deduction spans credit blocks. 3. If `invoice_settings` is specified, an invoice will be created that reflects the cost of the credits (based on `amount` and `per_unit_cost_basis`). ## Adding credits Adding credits is done by creating an entry of type `increment`. This requires the caller to specify a number of credits as well as an optional expiry date in `YYYY-MM-DD` format. Orb also recommends specifying a description to assist with auditing. When adding credits, the caller can also specify a cost basis per-credit, to indicate how much in USD a customer paid for a single credit in a block. This can later be used for revenue recognition. The following snippet illustrates a sample request body to increment credits which will expire in January of 2022. ```json { "entry_type": "increment", "amount": 100, "expiry_date": "2022-12-28", "per_unit_cost_basis": "0.20", "description": "Purchased 100 credits" } ``` Note that an `increment` entry always creates a new credit block (defined by the optional `effective_date` and `expiry_date`); it never modifies an existing block. ### Invoicing for credits By default, Orb manipulates the credit ledger but does not charge for credits. However, if you pass `invoice_settings` in the body of this request, Orb will also generate a one-off invoice for the customer for the credits pre-purchase. Note that you _must_ provide the `per_unit_cost_basis`, since the total charges on the invoice are calculated by multiplying the cost basis with the number of credit units added. If you invoice or handle payment of credits outside of Orb (i.e. marketplace customers), set `mark_as_paid` in the `invoice_settings` to `true` to prevent duplicate invoicing effects. * if `per_unit_cost_basis` is greater than zero, an invoice will be generated and `invoice_settings` must be included * if `invoice_settings` is passed, one of either `custom_due_date` or `net_terms` is required to determine the due date ## Deducting Credits Orb allows you to deduct credits from a customer by creating an entry of type `decrement`. A `decrement` entry records credits as usage and immediately recognizes revenue at the block's `per_unit_cost_basis`. For most credit removals, use `void` (no revenue impact) or `expiration_change` (revenue recognized on expiration) instead. Only use `decrement` when credits were genuinely consumed outside of normal event ingestion. Orb matches the algorithm for automatic deductions for determining which credit blocks to decrement from. In the case that the deduction leads to multiple ledger entries, the response from this endpoint will be the final deduction. Orb also optionally allows specifying a description to assist with auditing. The following snippet illustrates a sample request body to decrement credits. ```json { "entry_type": "decrement", "amount": 20, "description": "Removing excess credits" } ``` ## Changing credits expiry If you'd like to change when existing credits expire, you should create a ledger entry of type `expiration_change`. For this entry, the required parameter `expiry_date` identifies the _originating_ block, and the required parameter `target_expiry_date` identifies when the transferred credits should now expire. A new credit block will be created with expiry date `target_expiry_date`, with the same cost basis data as the original credit block, if present. Note that the balance of the block with the given `expiry_date` must be at least equal to the desired transfer amount determined by the `amount` parameter. The following snippet illustrates a sample request body to extend the expiration date of credits by one year: ```json { "entry_type": "expiration_change", "amount": 10, "expiry_date": "2022-12-28", "block_id": "UiUhFWeLHPrBY4Ad", "target_expiry_date": "2023-12-28", "description": "Extending credit validity" } ``` ## Voiding credits If you'd like to void a credit block, create a ledger entry of type `void`. For this entry, `block_id` is required to identify the block, and `amount` indicates how many credits to void, up to the block's initial balance. Pass in a `void_reason` of `refund` if the void is due to a refund. ## Amendment If you'd like to undo a decrement on a credit block, create a ledger entry of type `amendment`. For this entry, `block_id` is required to identify the block that was originally decremented from, and `amount` indicates how many credits to return to the customer, up to the block's initial balance. # Create top-up Source: https://docs.withorb.com/api-reference/credit/create-top-up /api-reference/orb-openapi.json post /customers/{customer_id}/credits/top_ups This endpoint allows you to create a new top-up for a specified customer's balance. While this top-up is active, the customer's balance will added in increments of the specified amount whenever the balance reaches the specified threshold. If a top-up already exists for this customer in the same currency, the existing top-up will be replaced. # Create top-up by external ID Source: https://docs.withorb.com/api-reference/credit/create-top-up-by-external-id /api-reference/orb-openapi.json post /customers/external_customer_id/{external_customer_id}/credits/top_ups This endpoint allows you to create a new top-up for a specified customer's balance. While this top-up is active, the customer's balance will added in increments of the specified amount whenever the balance reaches the specified threshold. If a top-up already exists for this customer in the same currency, the existing top-up will be replaced. # Deactivate top-up Source: https://docs.withorb.com/api-reference/credit/deactivate-top-up /api-reference/orb-openapi.json delete /customers/{customer_id}/credits/top_ups/{top_up_id} This deactivates the top-up and voids any invoices associated with pending credit blocks purchased through the top-up. # Deactivate top-up by external ID Source: https://docs.withorb.com/api-reference/credit/deactivate-top-up-by-external-id /api-reference/orb-openapi.json delete /customers/external_customer_id/{external_customer_id}/credits/top_ups/{top_up_id} This deactivates the top-up and voids any invoices associated with pending credit blocks purchased through the top-up. # Delete credit block Source: https://docs.withorb.com/api-reference/credit/delete-credit-block /api-reference/orb-openapi.json delete /credit_blocks/{block_id} This endpoint deletes a credit block by its ID. When a credit block is deleted: - The block is removed from the customer's credit ledger. - Any usage of the credit block is reversed, and the ledger is replayed as if the block never existed. - If invoices were generated from the purchase of the credit block, they will be deleted if in draft status, voided if issued, or a credit note will be issued if the invoice is paid. Issued invoices that had credits applied from this block will not be regenerated, but the ledger will reflect the state as if credits from the deleted block were never applied. # Fetch credit block Source: https://docs.withorb.com/api-reference/credit/fetch-credit-block /api-reference/orb-openapi.json get /credit_blocks/{block_id} This endpoint returns a credit block identified by its block_id. # Fetch customer credit balance Source: https://docs.withorb.com/api-reference/credit/fetch-customer-credit-balance /api-reference/orb-openapi.json get /customers/{customer_id}/credits Returns a paginated list of unexpired, non-zero credit blocks for a customer. If `include_all_blocks` is set to `true`, all credit blocks (including expired and depleted blocks) will be included in the response. Note that `currency` defaults to credits if not specified. To use a real world currency, set `currency` to an ISO 4217 string. Results can be filtered by the block's `effective_date` using the `effective_date[gte]`, `effective_date[gt]`, `effective_date[lt]`, and `effective_date[lte]` query parameters. This filters on when the credit block becomes effective, which may differ from creation time for backdated credits. # Fetch customer credit balance by external customer id Source: https://docs.withorb.com/api-reference/credit/fetch-customer-credit-balance-by-external-customer-id /api-reference/orb-openapi.json get /customers/external_customer_id/{external_customer_id}/credits Returns a paginated list of unexpired, non-zero credit blocks for a customer. If `include_all_blocks` is set to `true`, all credit blocks (including expired and depleted blocks) will be included in the response. Note that `currency` defaults to credits if not specified. To use a real world currency, set `currency` to an ISO 4217 string. Results can be filtered by the block's `effective_date` using the `effective_date[gte]`, `effective_date[gt]`, `effective_date[lt]`, and `effective_date[lte]` query parameters. This filters on when the credit block becomes effective, which may differ from creation time for backdated credits. # Fetch customer credits ledger Source: https://docs.withorb.com/api-reference/credit/fetch-customer-credits-ledger /api-reference/orb-openapi.json get /customers/{customer_id}/credits/ledger The credits ledger provides _auditing_ functionality over Orb's credits system with a list of actions that have taken place to modify a customer's credit balance. This [paginated endpoint](/api-reference/pagination) lists these entries, starting from the most recent ledger entry. More details on using Orb's real-time credit feature are [here](/product-catalog/prepurchase). There are four major types of modifications to credit balance, detailed below. ## Increment Credits (which optionally expire on a future date) can be added via the API ([Add Ledger Entry](create-ledger-entry)). The ledger entry for such an action will always contain the total eligible starting and ending balance for the customer at the time the entry was added to the ledger. ## Decrement Deductions can occur as a result of an API call to create a ledger entry (see [Add Ledger Entry](create-ledger-entry)), or automatically as a result of incurring usage. Both ledger entries present the `decrement` entry type. As usage for a customer is reported into Orb, credits may be deducted according to the customer's plan configuration. An automated deduction of this type will result in a ledger entry, also with a starting and ending balance. Each day's usage for a particular price, invoice, and block will be grouped into a single entry. By default, Orb uses an algorithm that automatically deducts from the *soonest expiring credit block* first in order to ensure that all credits are utilized appropriately. As an example, if trial credits with an expiration date of 2 weeks from now are present for a customer, they will be used before any deductions take place from a non-expiring credit block. If there are multiple blocks with the same expiration date, Orb will deduct from the block with the *lower cost basis* first (e.g. trial credits with a \$0 cost basis before paid credits with a \$5.00 cost basis). It's also possible for a single usage event's deduction to _span_ credit blocks. In this case, Orb will deduct from the next block, ending at the credit block which consists of unexpiring credits. Each of these deductions will lead to a _separate_ ledger entry, one per credit block that is deducted from. By default, the customer's total credit balance in Orb can be negative as a result of a decrement. ## Expiration change The expiry of credits can be changed as a result of the API (See [Add Ledger Entry](create-ledger-entry)). This will create a ledger entry that specifies the balance as well as the initial and target expiry dates. Note that for this entry type, `starting_balance` will equal `ending_balance`, and the `amount` represents the balance transferred. The credit block linked to the ledger entry is the source credit block from which there was an expiration change. ## Credits expiry When a set of credits expire on pre-set expiration date, the customer's balance automatically reflects this change and adds an entry to the ledger indicating this event. Note that credit expiry should always happen close to a date boundary in the customer's timezone. ## Void initiated Credit blocks can be voided via the API. The `amount` on this entry corresponds to the number of credits that were remaining in the block at time of void. `void_reason` will be populated if the void is created with a reason. ## Void When a set of credits is voided, the customer's balance automatically reflects this change and adds an entry to the ledger indicating this event. ## Amendment When credits are added to a customer's balance as a result of a correction, this entry will be added to the ledger to indicate the adjustment of credits. # Fetch customer credits ledger by external ID Source: https://docs.withorb.com/api-reference/credit/fetch-customer-credits-ledger-by-external-id /api-reference/orb-openapi.json get /customers/external_customer_id/{external_customer_id}/credits/ledger The credits ledger provides _auditing_ functionality over Orb's credits system with a list of actions that have taken place to modify a customer's credit balance. This [paginated endpoint](/api-reference/pagination) lists these entries, starting from the most recent ledger entry. More details on using Orb's real-time credit feature are [here](/product-catalog/prepurchase). There are four major types of modifications to credit balance, detailed below. ## Increment Credits (which optionally expire on a future date) can be added via the API ([Add Ledger Entry](create-ledger-entry)). The ledger entry for such an action will always contain the total eligible starting and ending balance for the customer at the time the entry was added to the ledger. ## Decrement Deductions can occur as a result of an API call to create a ledger entry (see [Add Ledger Entry](create-ledger-entry)), or automatically as a result of incurring usage. Both ledger entries present the `decrement` entry type. As usage for a customer is reported into Orb, credits may be deducted according to the customer's plan configuration. An automated deduction of this type will result in a ledger entry, also with a starting and ending balance. Each day's usage for a particular price, invoice, and block will be grouped into a single entry. By default, Orb uses an algorithm that automatically deducts from the *soonest expiring credit block* first in order to ensure that all credits are utilized appropriately. As an example, if trial credits with an expiration date of 2 weeks from now are present for a customer, they will be used before any deductions take place from a non-expiring credit block. If there are multiple blocks with the same expiration date, Orb will deduct from the block with the *lower cost basis* first (e.g. trial credits with a \$0 cost basis before paid credits with a \$5.00 cost basis). It's also possible for a single usage event's deduction to _span_ credit blocks. In this case, Orb will deduct from the next block, ending at the credit block which consists of unexpiring credits. Each of these deductions will lead to a _separate_ ledger entry, one per credit block that is deducted from. By default, the customer's total credit balance in Orb can be negative as a result of a decrement. ## Expiration change The expiry of credits can be changed as a result of the API (See [Add Ledger Entry](create-ledger-entry)). This will create a ledger entry that specifies the balance as well as the initial and target expiry dates. Note that for this entry type, `starting_balance` will equal `ending_balance`, and the `amount` represents the balance transferred. The credit block linked to the ledger entry is the source credit block from which there was an expiration change. ## Credits expiry When a set of credits expire on pre-set expiration date, the customer's balance automatically reflects this change and adds an entry to the ledger indicating this event. Note that credit expiry should always happen close to a date boundary in the customer's timezone. ## Void initiated Credit blocks can be voided via the API. The `amount` on this entry corresponds to the number of credits that were remaining in the block at time of void. `void_reason` will be populated if the void is created with a reason. ## Void When a set of credits is voided, the customer's balance automatically reflects this change and adds an entry to the ledger indicating this event. ## Amendment When credits are added to a customer's balance as a result of a correction, this entry will be added to the ledger to indicate the adjustment of credits. # List invoices for credit block Source: https://docs.withorb.com/api-reference/credit/list-invoices-for-credit-block /api-reference/orb-openapi.json get /credit_blocks/{block_id}/invoices This endpoint returns the credit block and its associated purchasing invoices. If a credit block was purchased (as opposed to being manually added), this endpoint returns the invoices that were created to charge the customer for the credit block. For credit blocks with payment schedules spanning multiple periods (e.g., monthly payments over 12 months), multiple invoices will be returned. For credit blocks created by subscription allocation prices, this endpoint returns the subscription invoice containing the allocation line item that created the block. If the credit block was not purchased (e.g., manual increment), an empty invoices list is returned. **Note: This endpoint is currently experimental and its interface may change in future releases. Please contact support before building production integrations against this endpoint.** # List top-ups Source: https://docs.withorb.com/api-reference/credit/list-top-ups /api-reference/orb-openapi.json get /customers/{customer_id}/credits/top_ups This endpoint returns a list of a customer's active top-ups; a top-up that has been deactivated is not included. While a top-up is active, the customer's balance is increased by the top-up amount whenever it falls to the top-up's threshold. The response also includes pagination_metadata, which lets the caller retrieve the next page of results if they exist. # List top-ups by external ID Source: https://docs.withorb.com/api-reference/credit/list-top-ups-by-external-id /api-reference/orb-openapi.json get /customers/external_customer_id/{external_customer_id}/credits/top_ups This endpoint returns a list of a customer's active top-ups; a top-up that has been deactivated is not included. While a top-up is active, the customer's balance is increased by the top-up amount whenever it falls to the top-up's threshold. The response also includes pagination_metadata, which lets the caller retrieve the next page of results if they exist. # Create customer Source: https://docs.withorb.com/api-reference/customer/create-customer /api-reference/orb-openapi.json post /customers This operation is used to create an Orb customer, who is party to the core billing relationship. See [Customer](/core-concepts##customer) for an overview of the customer resource. This endpoint is critical in the following Orb functionality: * Automated charges can be configured by setting `payment_provider` and `payment_provider_id` to automatically issue invoices * [Customer ID Aliases](/events-and-metrics/customer-aliases) can be configured by setting `external_customer_id` * [Timezone localization](/essentials/timezones) can be configured on a per-customer basis by setting the `timezone` parameter # Create customer balance transaction Source: https://docs.withorb.com/api-reference/customer/create-customer-balance-transaction /api-reference/orb-openapi.json post /customers/{customer_id}/balance_transactions Creates an immutable balance transaction that updates the customer's balance and returns back the newly created transaction. # Create portal session Source: https://docs.withorb.com/api-reference/customer/create-portal-session /api-reference/orb-openapi.json post /customers/{customer_id}/portal_sessions Creates a portal session for the customer, returning a short-lived URL that provides authenticated access to the customer's billing portal. The session expires after `expires_in_minutes` (default 60, max 180). By default, creating a new session invalidates any other active portal sessions for the customer; pass `invalidate_existing=false` to allow concurrent sessions. # Create portal session by external ID Source: https://docs.withorb.com/api-reference/customer/create-portal-session-by-external-id /api-reference/orb-openapi.json post /customers/external_customer_id/{external_customer_id}/portal_sessions Creates a portal session for the customer, returning a short-lived URL that provides authenticated access to the customer's billing portal. The session expires after `expires_in_minutes` (default 60, max 180). By default, creating a new session invalidates any other active portal sessions for the customer; pass `invalidate_existing=false` to allow concurrent sessions. # Delete customer Source: https://docs.withorb.com/api-reference/customer/delete-customer /api-reference/orb-openapi.json delete /customers/{customer_id} This performs a deletion of this customer, its subscriptions, and its invoices, provided the customer does not have any issued invoices. Customers with issued invoices cannot be deleted. This operation is irreversible. Note that this is a _soft_ deletion, but the data will be inaccessible through the API and Orb dashboard. For a hard-deletion, please reach out to the Orb team directly. **Note**: This operation happens asynchronously and can be expected to take a few minutes to propagate to related resources. However, querying for the customer on subsequent GET requests while deletion is in process will reflect its deletion. # Fetch customer Source: https://docs.withorb.com/api-reference/customer/fetch-customer /api-reference/orb-openapi.json get /customers/{customer_id} This endpoint is used to fetch customer details given an identifier. If the `Customer` is in the process of being deleted, only the properties `id` and `deleted: true` will be returned. See the [Customer resource](/core-concepts#customer) for a full discussion of the Customer model. # Fetch customer by external ID Source: https://docs.withorb.com/api-reference/customer/fetch-customer-by-external-id /api-reference/orb-openapi.json get /customers/external_customer_id/{external_customer_id} This endpoint is used to fetch customer details given an `external_customer_id` (see [Customer ID Aliases](/events-and-metrics/customer-aliases)). Note that the resource and semantics of this endpoint exactly mirror [Get Customer](fetch-customer). # Fetch customer costs Source: https://docs.withorb.com/api-reference/customer/fetch-customer-costs /api-reference/orb-openapi.json get /customers/{customer_id}/costs This endpoint is used to fetch a day-by-day snapshot of a customer's costs in Orb, calculated by applying pricing information to the underlying usage (see the [subscription usage endpoint](/api-reference/subscription/fetch-subscription-usage) to fetch usage per metric, in usage units rather than a currency). This endpoint can be leveraged for internal tooling and to provide a more transparent billing experience for your end users: 1. Understand the cost breakdown per line item historically and in real-time for the current billing period. 2. Provide customer visibility into how different services are contributing to the overall invoice with a per-day timeseries (as compared to the [upcoming invoice](/api-reference/invoice/fetch-upcoming-invoice) resource, which represents a snapshot for the current period). 3. Assess how minimums and discounts affect your customers by teasing apart costs directly as a result of usage, as opposed to minimums and discounts at the plan and price level. 4. Gain insight into key customer health metrics, such as the percent utilization of the minimum committed spend. ## Fetching subscriptions By default, this endpoint fetches the currently active subscription for the customer, and returns cost information for the subscription's current billing period, broken down by each participating price. If there are no currently active subscriptions, this will instead default to the most recently active subscription or return an empty series if none are found. For example, if your plan charges for compute hours, job runs, and data syncs, then this endpoint would provide a daily breakdown of your customer's cost for each of those axes. If timeframe bounds are specified, Orb fetches all subscriptions that were active in that timeframe. If two subscriptions overlap on a single day, costs from each price will be summed, and prices for both subscriptions will be included in the breakdown. ## Prepaid plans For plans that include prices which deduct credits rather than accrue in-arrears charges in a billable currency, this endpoint will return the total deduction amount, in credits, for the specified timeframe. ## Cumulative subtotals and totals Since the subtotal and total must factor in any billing-period level discounts and minimums, it's most meaningful to consider costs relative to the start of the subscription's billing period. As a result, by default this endpoint returns cumulative totals since the beginning of the billing period. In particular, the `timeframe_start` of a returned timeframe window is *always* the beginning of the billing period and `timeframe_end` is incremented one day at a time to build the result. A customer that uses a few API calls a day but has a minimum commitment might exhibit the following pattern for their subtotal and total in the first few days of the month. Here, we assume that each API call is \$2.50, the customer's plan has a monthly minimum of \$50 for this price, and that the subscription's billing period bounds are aligned to the first of the month: | timeframe_start | timeframe_end | Cumulative usage | Subtotal | Total (incl. commitment) | | -----------| ----------- | ----------- | ----------- |----------- | | 2023-02-01 | 2023-02-02 | 9 | \$22.50 | \$50.00 | | 2023-02-01 | 2023-02-03 | 19 | \$47.50 | \$50.00 | | 2023-02-01 | 2023-02-04 | 20 | \$50.00 | \$50.00 | | 2023-02-01 | 2023-02-05 | 28 | \$70.00 | \$70.00 | | 2023-02-01 | 2023-02-06 | 36 | \$90.00 | \$90.00 | ### Periodic values When the query parameter `view_mode=periodic` is specified, Orb will return an incremental day-by-day view of costs. In this case, there will always be a one-day difference between `timeframe_start` and `timeframe_end` for the timeframes returned. This is a transform on top of the cumulative costs, calculated by taking the difference of each timeframe with the last. Note that in the above example, the `Total` value would be 0 for the second two data points, since the minimum commitment has not yet been hit and each day is not contributing anything to the total cost. ## Timeframe bounds For an active subscription, both timeframes should be specified in the request. If a subscription starts or ends within the timeframe, the response will only include windows where the subscription is active. If a subscription has ended, no timeframe bounds need to be specified and the response will default to the billing period when the subscription was last active. As noted above, `timeframe_start` for a given cumulative datapoint is always the beginning of the billing period, and `timeframe_end` is incremented one day at a time to construct the response. When a timeframe is passed in that is not aligned to the current subscription's billing period, the response will contain cumulative totals from multiple billing periods. Suppose the queried customer has a subscription aligned to the 15th of every month. If this endpoint is queried with the date range `2023-06-01` - `2023-07-01`, the first data point will represent about half a billing period's worth of costs, accounting for accruals from the start of the billing period and inclusive of the first day of the timeframe (`timeframe_start = 2023-05-15 00:00:00`, `timeframe_end = 2023-06-02 00:00:00`) | datapoint index | timeframe_start | timeframe_end | | ----------- | -----------| ----------- | | 0 | 2023-05-15 | 2023-06-02 | | 1 | 2023-05-15 | 2023-06-03 | | 2 | ... | ... | | 3 | 2023-05-15 | 2023-06-14 | | 4 | 2023-06-15 | 2023-06-16 | | 5 | 2023-06-15 | 2023-06-17 | | 6 | ... | ... | | 7 | 2023-06-15 | 2023-07-01 | You can see this sliced timeframe visualized [here](https://i.imgur.com/TXhYgme.png). ### Matrix prices When a price uses matrix pricing, it's important to view costs grouped by those matrix dimensions. Orb will return `price_groups` with the `grouping_key` and `secondary_grouping_key` based on the matrix price definition, for each `grouping_value` and `secondary_grouping_value` available. ## Trials During a trial, usage prices carry a trial discount, capped at the plan's trial maximum amount (or 100% when no maximum is set), and the trial period is billed as a single invoice dated at the trial's end. In the costs response, trial-period usage appears at full list price in `subtotal`, with the trial discount reflected in `total`: `total` is \$0 as long as usage stays within the trial maximum, and usage beyond the maximum bills normally. Fixed fees are not charged during a trial. # Fetch customer costs by external ID Source: https://docs.withorb.com/api-reference/customer/fetch-customer-costs-by-external-id /api-reference/orb-openapi.json get /customers/external_customer_id/{external_customer_id}/costs This endpoint is used to fetch a day-by-day snapshot of a customer's costs in Orb, calculated by applying pricing information to the underlying usage (see the [subscription usage endpoint](/api-reference/subscription/fetch-subscription-usage) to fetch usage per metric, in usage units rather than a currency). This endpoint can be leveraged for internal tooling and to provide a more transparent billing experience for your end users: 1. Understand the cost breakdown per line item historically and in real-time for the current billing period. 2. Provide customer visibility into how different services are contributing to the overall invoice with a per-day timeseries (as compared to the [upcoming invoice](/api-reference/invoice/fetch-upcoming-invoice) resource, which represents a snapshot for the current period). 3. Assess how minimums and discounts affect your customers by teasing apart costs directly as a result of usage, as opposed to minimums and discounts at the plan and price level. 4. Gain insight into key customer health metrics, such as the percent utilization of the minimum committed spend. ## Fetching subscriptions By default, this endpoint fetches the currently active subscription for the customer, and returns cost information for the subscription's current billing period, broken down by each participating price. If there are no currently active subscriptions, this will instead default to the most recently active subscription or return an empty series if none are found. For example, if your plan charges for compute hours, job runs, and data syncs, then this endpoint would provide a daily breakdown of your customer's cost for each of those axes. If timeframe bounds are specified, Orb fetches all subscriptions that were active in that timeframe. If two subscriptions overlap on a single day, costs from each price will be summed, and prices for both subscriptions will be included in the breakdown. ## Prepaid plans For plans that include prices which deduct credits rather than accrue in-arrears charges in a billable currency, this endpoint will return the total deduction amount, in credits, for the specified timeframe. ## Cumulative subtotals and totals Since the subtotal and total must factor in any billing-period level discounts and minimums, it's most meaningful to consider costs relative to the start of the subscription's billing period. As a result, by default this endpoint returns cumulative totals since the beginning of the billing period. In particular, the `timeframe_start` of a returned timeframe window is *always* the beginning of the billing period and `timeframe_end` is incremented one day at a time to build the result. A customer that uses a few API calls a day but has a minimum commitment might exhibit the following pattern for their subtotal and total in the first few days of the month. Here, we assume that each API call is \$2.50, the customer's plan has a monthly minimum of \$50 for this price, and that the subscription's billing period bounds are aligned to the first of the month: | timeframe_start | timeframe_end | Cumulative usage | Subtotal | Total (incl. commitment) | | -----------| ----------- | ----------- | ----------- |----------- | | 2023-02-01 | 2023-02-02 | 9 | \$22.50 | \$50.00 | | 2023-02-01 | 2023-02-03 | 19 | \$47.50 | \$50.00 | | 2023-02-01 | 2023-02-04 | 20 | \$50.00 | \$50.00 | | 2023-02-01 | 2023-02-05 | 28 | \$70.00 | \$70.00 | | 2023-02-01 | 2023-02-06 | 36 | \$90.00 | \$90.00 | ### Periodic values When the query parameter `view_mode=periodic` is specified, Orb will return an incremental day-by-day view of costs. In this case, there will always be a one-day difference between `timeframe_start` and `timeframe_end` for the timeframes returned. This is a transform on top of the cumulative costs, calculated by taking the difference of each timeframe with the last. Note that in the above example, the `Total` value would be 0 for the second two data points, since the minimum commitment has not yet been hit and each day is not contributing anything to the total cost. ## Timeframe bounds For an active subscription, both timeframes should be specified in the request. If a subscription starts or ends within the timeframe, the response will only include windows where the subscription is active. If a subscription has ended, no timeframe bounds need to be specified and the response will default to the billing period when the subscription was last active. As noted above, `timeframe_start` for a given cumulative datapoint is always the beginning of the billing period, and `timeframe_end` is incremented one day at a time to construct the response. When a timeframe is passed in that is not aligned to the current subscription's billing period, the response will contain cumulative totals from multiple billing periods. Suppose the queried customer has a subscription aligned to the 15th of every month. If this endpoint is queried with the date range `2023-06-01` - `2023-07-01`, the first data point will represent about half a billing period's worth of costs, accounting for accruals from the start of the billing period and inclusive of the first day of the timeframe (`timeframe_start = 2023-05-15 00:00:00`, `timeframe_end = 2023-06-02 00:00:00`) | datapoint index | timeframe_start | timeframe_end | | ----------- | -----------| ----------- | | 0 | 2023-05-15 | 2023-06-02 | | 1 | 2023-05-15 | 2023-06-03 | | 2 | ... | ... | | 3 | 2023-05-15 | 2023-06-14 | | 4 | 2023-06-15 | 2023-06-16 | | 5 | 2023-06-15 | 2023-06-17 | | 6 | ... | ... | | 7 | 2023-06-15 | 2023-07-01 | You can see this sliced timeframe visualized [here](https://i.imgur.com/TXhYgme.png). ### Matrix prices When a price uses matrix pricing, it's important to view costs grouped by those matrix dimensions. Orb will return `price_groups` with the `grouping_key` and `secondary_grouping_key` based on the matrix price definition, for each `grouping_value` and `secondary_grouping_value` available. ## Trials During a trial, usage prices carry a trial discount, capped at the plan's trial maximum amount (or 100% when no maximum is set), and the trial period is billed as a single invoice dated at the trial's end. In the costs response, trial-period usage appears at full list price in `subtotal`, with the trial discount reflected in `total`: `total` is \$0 as long as usage stays within the trial maximum, and usage beyond the maximum bills normally. Fixed fees are not charged during a trial. # List balance transactions Source: https://docs.withorb.com/api-reference/customer/list-balance-transactions /api-reference/orb-openapi.json get /customers/{customer_id}/balance_transactions ## The customer balance The customer balance is an amount in the customer's currency, which Orb automatically applies to subsequent invoices. This balance can be adjusted manually via Orb's webapp on the customer details page. You can use this balance to provide a fixed mid-period credit to the customer. Commonly, this is done due to system downtime/SLA violation, or an adhoc adjustment discussed with the customer. If the balance is a positive value at the time of invoicing, it represents that the customer has credit that should be used to offset the amount due on the next issued invoice. In this case, Orb will automatically reduce the next invoice by the balance amount, and roll over any remaining balance if the invoice is fully discounted. If the balance is a negative value at the time of invoicing, Orb will increase the invoice's amount due with a positive adjustment, and reset the balance to 0. This endpoint retrieves all customer balance transactions in reverse chronological order for a single customer, providing a complete audit trail of all adjustments and invoice applications. # List customers Source: https://docs.withorb.com/api-reference/customer/list-customers /api-reference/orb-openapi.json get /customers This endpoint returns a list of all customers for an account. The list of customers is ordered starting from the most recently created customer. This endpoint follows Orb's [standardized pagination format](/api-reference/pagination). See [Customer](/core-concepts##customer) for an overview of the customer model. # Update customer Source: https://docs.withorb.com/api-reference/customer/update-customer /api-reference/orb-openapi.json put /customers/{customer_id} This endpoint can be used to update the `payment_provider`, `payment_provider_id`, `name`, `email`, `email_delivery`, `tax_id`, `auto_collection`, `metadata`, `shipping_address`, `billing_address`, `additional_emails`, and `currency` of an existing customer. `currency` can only be set if it has not already been set on the customer. Other fields on a customer are currently immutable. # Update customer by external ID Source: https://docs.withorb.com/api-reference/customer/update-customer-by-external-id /api-reference/orb-openapi.json put /customers/external_customer_id/{external_customer_id} This endpoint is used to update customer details given an `external_customer_id` (see [Customer ID Aliases](/events-and-metrics/customer-aliases)). Note that the resource and semantics of this endpoint exactly mirror [Update Customer](update-customer). # Update payment methods from payment provider Source: https://docs.withorb.com/api-reference/customer/update-payment-methods-from-payment-provider /api-reference/orb-openapi.json post /customers/external_customer_id/{external_customer_id}/sync_payment_methods_from_gateway Sync Orb's payment methods for the customer with their gateway. This method can be called before taking an action that may cause the customer to be charged, ensuring that the most up-to-date payment method is charged. **Note**: This functionality is currently only available for Stripe. # Update payment methods from payment provider Source: https://docs.withorb.com/api-reference/customer/update-payment-methods-from-payment-provider-1 /api-reference/orb-openapi.json post /customers/{customer_id}/sync_payment_methods_from_gateway Sync Orb's payment methods for the customer with their gateway. This method can be called before taking an action that may cause the customer to be charged, ensuring that the most up-to-date payment method is charged. **Note**: This functionality is currently only available for Stripe. # Create dimensional price group Source: https://docs.withorb.com/api-reference/dimensional-price-group/create-dimensional-price-group /api-reference/orb-openapi.json post /dimensional_price_groups A dimensional price group is used to partition the result of a billable metric by a set of dimensions. Prices in a price group must specify the partition used to derive their usage. For example, suppose we have a billable metric that measures the number of widgets used and we want to charge differently depending on the color of the widget. We can create a price group with a dimension "color" and two prices: one that charges \$10 per red widget and one that charges \$20 per blue widget. # Fetch dimensional price group Source: https://docs.withorb.com/api-reference/dimensional-price-group/fetch-dimensional-price-group /api-reference/orb-openapi.json get /dimensional_price_groups/{dimensional_price_group_id} This endpoint returns a dimensional price group identified by its ID. A dimensional price group partitions the result of a billable metric by a set of dimensions, and the prices in the group specify which partition their usage is derived from. # Fetch dimensional price group by external ID Source: https://docs.withorb.com/api-reference/dimensional-price-group/fetch-dimensional-price-group-by-external-id /api-reference/orb-openapi.json get /dimensional_price_groups/external_dimensional_price_group_id/{external_dimensional_price_group_id} This endpoint returns a dimensional price group identified by the external ID you assigned it, rather than by its Orb ID. A dimensional price group partitions the result of a billable metric by a set of dimensions, and the prices in the group specify which partition their usage is derived from. # List dimensional price groups Source: https://docs.withorb.com/api-reference/dimensional-price-group/list-dimensional-price-groups /api-reference/orb-openapi.json get /dimensional_price_groups This endpoint returns a list of all dimensional price groups for an account. A dimensional price group partitions the result of a billable metric by a set of dimensions, and the prices in the group specify which partition their usage is derived from. The response also includes pagination_metadata, which lets the caller retrieve the next page of results if they exist. # Update dimensional price group Source: https://docs.withorb.com/api-reference/dimensional-price-group/update-dimensional-price-group /api-reference/orb-openapi.json put /dimensional_price_groups/{dimensional_price_group_id} This endpoint can be used to update the `external_dimensional_price_group_id` and `metadata` of an existing dimensional price group. Other fields on a dimensional price group are currently immutable. # Update dimensional price group by external ID Source: https://docs.withorb.com/api-reference/dimensional-price-group/update-dimensional-price-group-by-external-id /api-reference/orb-openapi.json put /dimensional_price_groups/external_dimensional_price_group_id/{external_dimensional_price_group_id} This endpoint can be used to update the `external_dimensional_price_group_id` and `metadata` of an existing dimensional price group. Other fields on a dimensional price group are currently immutable. # Error responses Source: https://docs.withorb.com/api-reference/error-responses Orb's error response format follows [RFC 7807](https://datatracker.ietf.org/doc/html/rfc7807). Each error response includes: * `status`: Same as the HTTP status code * `title`: Brief description of the error message * `type`: URI that always links to this document for further reference * `detail`: Human readable description providing more insight into the error Note that error responses may include other keys specific to the error. ```json theme={null} { "detail": "Could not find a resource of type Plan with identifier: DBhwNcMNfYgeBpn.", "status": 404, "title": "A specified resource was not found.", "type": "https://docs.withorb.com/reference/error-responses#404-resource-not-found" } ``` The following table describes the error types. If you are processing an error programmatically, consider using the `type` URI along with the status code. The `type` URI is a stable identifier for the class of error. | Title | Status code | Description | `type` uri | | --------------------------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | | Authentication failure | 401 | Your request did not provide a valid or well-formatted API key. | `https://docs.withorb.com/reference/error-responses#401-authentication-error` | | Request validation error | 400 | A request that fails validation will return human-readable information about what went wrong. The error response for this includes an array of `validation_errors`. | `https://docs.withorb.com/reference/error-responses#400-request-validation-errors` | | Duplicate resource creation | 400 | Returned when the ID passed to the creation request already exists. | `https://docs.withorb.com/reference/error-responses#400-duplicate-resource-creation` | | Resource not found | 404 | This is returned when a provided identifier does not map to a resource that is accessible by the current account. | `https://docs.withorb.com/reference/error-responses#404-resource-not-found` | | 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. | `https://docs.withorb.com/reference/error-responses#404-feature-not-available` | | URL not found | 404 | Returned when the requested URL was not found on the server. | `https://docs.withorb.com/reference/error-responses#404-url-not-found` | | Resource conflict | 409 | Returned when the request conflicts with another concurrent request that is using the same idempotency key or if the same idempotency key has been used for a request with different parameters. Note that this may also be returned from specific endpoints that require a consistent view of a resource (e.g. the credits ledger) when concurrent requests attempt to modify it. **Important**: Orb does not cache these errors for idempotency reasons, meaning these requests will not be replayed because request processing has not started. Retrying these requests should be safe. | `https://docs.withorb.com/reference/error-responses#409-resource-conflict` | | Too many requests | 429 | Returned when the request rate exceeds the limit. | `https://docs.withorb.com/reference/error-responses#429-too-many-requests` | | Internal Server error | 500 | This is an internal server crash. The Orb team has been notified! | `https://docs.withorb.com/reference/error-responses#500-internal-server-error` | # Amend event Source: https://docs.withorb.com/api-reference/event/amend-event /api-reference/orb-openapi.json put /events/{event_id} This endpoint is used to amend a single usage event with a given `event_id`. `event_id` refers to the `idempotency_key` passed in during ingestion. The event will maintain its existing `event_id` after the amendment. This endpoint will mark the existing event as ignored, and Orb will only use the new event passed in the body of this request as the source of truth for that `event_id`. Note that a single event can be amended any number of times, so the same event can be overwritten in subsequent calls to this endpoint. Only a single event with a given `event_id` will be considered the source of truth at any given time. This is a powerful and audit-safe mechanism to retroactively update a single event in cases where you need to: * update an event with new metadata as you iterate on your pricing model * update an event based on the result of an external API call (e.g. call to a payment gateway succeeded or failed) This amendment API is always audit-safe. The process will still retain the original event, though it will be ignored for billing calculations. For auditing and data fidelity purposes, Orb never overwrites or permanently deletes ingested usage data. ## Request validation * The `timestamp` of the new event must match the `timestamp` of the existing event already ingested. As with ingestion, all timestamps must be sent in ISO8601 format with UTC timezone offset. * The `customer_id` or `external_customer_id` of the new event must match the `customer_id` or `external_customer_id` of the existing event already ingested. Exactly one of `customer_id` and `external_customer_id` should be specified, and similar to ingestion, the ID must identify a Customer resource within Orb. Unlike ingestion, for event amendment, we strictly enforce that the Customer must be in the Orb system, even during the initial integration period. We do not allow updating the `Customer` an event is associated with. * Orb does not accept an `idempotency_key` with the event in this endpoint, since this request is by design idempotent. On retryable errors, you should retry the request and assume the amendment operation has not succeeded until receipt of a 2xx. * The event's `timestamp` must fall within the customer's current subscription's billing period, or within the grace period of the customer's current subscription's previous billing period. * By default, no more than 100 events can be amended for a single customer in a 100 day period. For higher volume updates, consider using the [event backfill](create-backfill) endpoint. # Close backfill Source: https://docs.withorb.com/api-reference/event/close-backfill /api-reference/orb-openapi.json post /events/backfills/{backfill_id}/close Closing a backfill makes the updated usage visible in Orb. Upon closing a backfill, Orb will asynchronously reflect the updated usage in invoice amounts and usage graphs. Once all of the updates are complete, the backfill's status will transition to `reflected`. # Create backfill Source: https://docs.withorb.com/api-reference/event/create-backfill /api-reference/orb-openapi.json post /events/backfills Creating the backfill enables adding or replacing past events, even those that are older than the ingestion grace period. Performing a backfill in Orb involves 3 steps: 1. Create the backfill, specifying its parameters. 2. [Ingest](/api-reference/event/ingest-events) usage events, referencing the backfill (query parameter `backfill_id`). 3. [Close](close-backfill) the backfill, propagating the update in past usage throughout Orb. Changes from a backfill are not reflected until the backfill is closed, so you won’t need to worry about your customers seeing partially updated usage data. Backfills are also reversible, so you’ll be able to revert a backfill if you’ve made a mistake. This endpoint will return a backfill object, which contains an `id`. That `id` can then be used as the `backfill_id` query parameter to the event ingestion endpoint to associate ingested events with this backfill. The effects (e.g. updated usage graphs) of this backfill will not take place until the backfill is closed. If the `replace_existing_events` is `true`, existing events in the backfill's timeframe will be replaced with the newly ingested events associated with the backfill. If `false`, newly ingested events will be added to the existing events. If a `customer_id` or `external_customer_id` is specified, the backfill will only affect events for that customer. If neither is specified, the backfill will affect all customers. When `replace_existing_events` is `true`, this indicates that existing events in the timeframe should no longer be counted towards invoiced usage. In this scenario, the parameter `deprecation_filter` can be optionally added which enables filtering using [computed properties](/extensibility/advanced-metrics#computed-properties). The expressiveness of computed properties allows you to deprecate existing events based on both a period of time and specific property values. You may not have multiple backfills in a pending or pending_revert state with overlapping timeframes. # Deprecate event Source: https://docs.withorb.com/api-reference/event/deprecate-event /api-reference/orb-openapi.json put /events/{event_id}/deprecate This endpoint is used to deprecate a single usage event with a given `event_id`. `event_id` refers to the `idempotency_key` passed in during ingestion. This endpoint will mark the existing event as ignored. Note that if you attempt to re-ingest an event with the same `event_id` as a deprecated event, Orb will return an error. This is a powerful and audit-safe mechanism to retroactively deprecate a single event in cases where you need to: * no longer bill for an event that was improperly reported * no longer bill for an event based on the result of an external API call (e.g. call to a payment gateway failed and the user should not be billed) If you want to only change specific properties of an event, but keep the event as part of the billing calculation, use the [Amend event](amend-event) endpoint instead. This API is always audit-safe. The process will still retain the deprecated event, though it will be ignored for billing calculations. For auditing and data fidelity purposes, Orb never overwrites or permanently deletes ingested usage data. ## Request validation * Orb does not accept an `idempotency_key` with the event in this endpoint, since this request is by design idempotent. On retryable errors, you should retry the request and assume the deprecation operation has not succeeded until receipt of a 2xx. * The event's `timestamp` must fall within the customer's current subscription's billing period, or within the grace period of the customer's current subscription's previous billing period. Orb does not allow deprecating events for billing periods that have already invoiced customers. * The `customer_id` or the `external_customer_id` of the original event ingestion request must identify a Customer resource within Orb, even if this event was ingested during the initial integration period. We do not allow deprecating events for customers not in the Orb system. * By default, no more than 100 events can be deprecated for a single customer in a 100 day period. For higher volume updates, consider using the [event backfill](create-backfill) endpoint. # Fetch backfill Source: https://docs.withorb.com/api-reference/event/fetch-backfill /api-reference/orb-openapi.json get /events/backfills/{backfill_id} This endpoint is used to fetch a backfill given an identifier. # Get event volume Source: https://docs.withorb.com/api-reference/event/get-event-volume /api-reference/orb-openapi.json get /events/volume This endpoint returns the event volume for an account in a [paginated list format](/api-reference/pagination). The event volume is aggregated by the hour and the [timestamp](/api-reference/event/ingest-events) field is used to determine which hour an event is associated with. Note, this means that late-arriving events increment the volume count for the hour window the timestamp is in, not the latest hour window. Each item in the response contains the count of events aggregated by the hour where the start and end time are hour-aligned and in UTC. When a specific timestamp is passed in for either start or end time, the response includes the hours the timestamp falls in. # Ingest events Source: https://docs.withorb.com/api-reference/event/ingest-events /api-reference/orb-openapi.json post /ingest Orb's event ingestion model and API is designed around two core principles: 1. **Data fidelity**: The accuracy of your billing model depends on a robust foundation of events. Orb's API protocol encourages usage patterns that ensure that your data is consistently complete and correct. 2. **Fast integration**: Sending events into Orb requires no tedious setup steps or explicit field schema for your event shape, making it instant to start streaming in usage in real-time. ## Event shape Events are the starting point for all usage calculations in the system, and are simple at their core: ```ts { // customer_id and external_customer_id are used to // attribute usage to a given Customer. Exactly one of these // should be specified in a given ingestion event. // `customer_id` is the Orb generated identifier for the Customer, // which is returned from the Create customer API call. customer_id: string, // external_customer_id is an alternate identifier which is associated // with a Customer at creation time. This is treated as an alias for // customer_id, and is usually set to an identifier native to your system. external_customer_id: string, // A string name identifying the event, usually a usage // action. By convention, this should not contain any whitespace. event_name: string, // An ISO 8601 format date with no timezone offset. // This should represent the time that usage occurred // and is important to attribute usage to a given // billing period. See the notes below on determining the timestamp. // e.g. 2020-12-09T16:09:53Z timestamp: string, // A unique value, generated by the client, that is // used to de-duplicate events. // Exactly one event with a given // idempotency key will be ingested, which allows for // safe request retries. idempotency_key: string // Optional custom metadata to attach to the event. // This might include a numeric value used for aggregation, // or a string/boolean value used for filtering. // The schema of this dictionary need not be pre-declared, and // properties can be added at any time. properties: { [key: string]?: string | number | boolean, }, } ``` ## Required fields Because events streamed to Orb are meant to be as flexible as possible, there are only a few required fields in every event. - We recommend that `idempotency_key` are unique strings that you generated with V4 UUIDs, but only require that they uniquely identify an event (i.e. don’t collide). - The `timestamp` field in the event body will be used to determine which billable period a given event falls into. For example, with a monthly billing cycle starting from the first of December, Orb will calculate metrics based on events that fall into the range `12-01 00:00:00 <= timestamp < 01-01 00:00:00`. ## Logging metadata Orb allows tagging events with metadata using a flexible properties dictionary. Since Orb does not enforce a rigid schema for this field-set, key-value pairs can be added dynamically as your events evolve. This dictionary can be helpful for a wide variety of use cases: - Numeric properties on events like `compute_time_ms` can later be inputs to our flexible query engine to determine usage. - Logging a region or cluster with each event can help you provide customers more granular visibility into their usage. - If you are using matrix pricing and matching a matrix price key with a property, you should ensure the value for that property is sent as a string. We encourage logging this metadata with an eye towards future use cases to ensure full coverage for historical data. The datatype of the value in the properties dictionary is important for metric creation from an event source. Values that you wish to numerically aggregate should be of numeric type in the event. ## Determining event timestamp For cases where usage is being reported in real time as it is occurring, timestamp should correspond to the time that usage occurred. In cases where usage is reported in aggregate for a historical timeframe at a regular interval, we recommend setting the event `timestamp` to the midpoint of the interval. As an example, if you have an hourly reporter that sends data once an hour for the previous hour of usage, setting the `timestamp` to the half-hour mark will ensure that the usage is counted within the correct period. Note that other time-related fields (e.g. time elapsed) can be added to the properties dictionary as necessary. In cases where usage is reported in aggregate for a historical timeframe, the timestamp must be within the grace period set for your account. Events with `timestamp < current_time - grace_period` will not be accepted as a valid event, and will throw validation errors. Enforcing the grace period enables Orb to accurately map usage to the correct billing cycle and ensure that all usage is billed for in the corresponding billing period. In general, Orb does not expect events with future dated timestamps. In cases where the timestamp is 5 minutes ahead of the current time, the event will not be accepted as a valid event, and will throw validation errors. ## Event validation Orb’s validation ensures that you recognize errors in your events as quickly as possible, and the API provides informative error messages to help you fix problems quickly. We validate the following: - Exactly one of `customer_id` and `external_customer_id` should be specified. - If the `customer_id` is specified, the customer in Orb must exist. - If the `external_customer_id` is specified, the customer in Orb does not need to exist. Events will be attributed to any future customers with the `external_customer_id` on subscription creation. - `timestamp` must conform to ISO 8601 and represent a timestamp at most 5 minutes in the future. This timestamp should be sent in UTC timezone (no timezone offset). ## Idempotency and retry semantics Orb's idempotency guarantees allow you to implement safe retry logic in the event of network or machine failures, ensuring data fidelity. Each event in the request payload is associated with an idempotency key, and Orb guarantees that a single idempotency key will be successfully ingested at most once. Note that when Orb encounters events with duplicate idempotency keys and differing event bodies in a batch of events, the entire batch will be rejected. - Successful responses return a 200 HTTP status code. The response contains information about previously processed events. - Requests that return a `4xx` HTTP status code indicate a payload error and contain at least one event with a validation failure. An event with a validation failure can be re-sent to the ingestion endpoint (after the payload is fixed) with the original idempotency key since that key is not marked as processed. - Requests that return a `5xx` HTTP status code indicate a server-side failure. These requests should be retried in their entirety. ## API usage and limits The ingestion API is designed made for real-time streaming ingestion and architected for high throughput. Even if events are later deemed unnecessary or filtered out, we encourage you to log them to Orb if they may be relevant to billing calculations in the future. To take advantage of the real-time features of the Orb platform and avoid any chance of dropped events by producers, we recommend reporting events to Orb frequently. Optionally, events can also be briefly aggregated at the source, as this API accepts an array of event bodies. Orb does not currently enforce a hard rate-limit for API usage or a maximum request payload size, but please give us a heads up if you’re changing either of these factors by an order of magnitude from initial setup. #### Example: ingestion response ```json { "validation_failed": [] } ``` # List backfills Source: https://docs.withorb.com/api-reference/event/list-backfills /api-reference/orb-openapi.json get /events/backfills This endpoint returns a list of all backfills in a list format. The list of backfills is ordered starting from the most recently created backfill. The response also includes [`pagination_metadata`](/api-reference/pagination), which lets the caller retrieve the next page of results if they exist. # Revert backfill Source: https://docs.withorb.com/api-reference/event/revert-backfill /api-reference/orb-openapi.json post /events/backfills/{backfill_id}/revert Reverting a backfill undoes all the effects of closing the backfill. If the backfill is reflected, the status will transition to `pending_revert` while the effects of the backfill are undone. Once all effects are undone, the backfill will transition to `reverted`. If a backfill is reverted before its closed, no usage will be updated as a result of the backfill and it will immediately transition to `reverted`. # Search events Source: https://docs.withorb.com/api-reference/event/search-events /api-reference/orb-openapi.json post /events/search This endpoint returns a filtered set of events for an account in a [paginated list format](/api-reference/pagination). Note that this is a `POST` endpoint rather than a `GET` endpoint because it employs a JSON body for search criteria rather than query parameters, allowing for a more flexible search syntax. Note that a search criteria _must_ be specified. Currently, Orb supports the following criteria: - `event_ids`: This is an explicit array of IDs to filter by. Note that an event's ID is the `idempotency_key` that was originally used for ingestion. By default, Orb will not throw a `404` if no events matched, Orb will return an empty array for `data` instead. # Request idempotency Source: https://docs.withorb.com/api-reference/idempotency Orb's API supports idempotency, to allow safe request retries without duplicating actions. For example, an idempotency key can be used to ensure that a request to create a subscription never creates duplicate subscriptions. A unique idempotency key may be provided in the `Idempotency-Key` header for all `POST/PATCH `operations. Although the idempotency key is not required, it is strongly encouraged for all `POST/PATCH` requests. Idempotency keys for `GET/PUT/DELETE` requests will be ignored since these requests are idempotent by default. Users will be able to safely retry requests that include an `Idempotency-Key` within 48 hours. Keys will expire after 48 hours, and Orb may re-execute side-effects as a result. ```json theme={null} curl --request POST \ --url https://api.withorb.com/v1/subscriptions \ --header 'Idempotency-Key: U9djswkfm802dq2' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' ``` ## Response format All requests performed with an idempotency key will include an `Idempotency-Key` header in the response to signal acknowledgement and processing of the key. Orb will include `Idempotent-Replayed: true` in the response to signal that the response is being served from a previous action. This can be taken to mean that no new side-effects were executed. ## Generating idempotency keys Idempotency keys can be arbitrary strings with a max length of 64 characters. In order to generate a fully unique key, Orb recommends the use of UUIDs. However, in some cases, idempotency keys can be used to ensure that a given action in a source system maps to one mutation in Orb. For example, if each customer ID in your system should have at most one Customer in Orb, consider using your customer ID as the idempotency key for the creation call. ## Error cases Orb will return a 409 resource conflict error if the request was retried before the original request completed (concurrent request). Similarly, reusing an idempotency key with a different request payload will also result in a 409 resource conflict. **Important**: Orb does not cache `409 Conflict` errors for idempotency reasons. This means that these requests will not be replayed because request processing has not started. Retrying these requests should be safe. In the case of transient internal server errors, Orb will include the HTTP header `Transient-Error` with the value `true`. Such requests can be safely retried, and clients can reuse the same idempotency key within 48 hours to retry the request. # Create a one-off invoice Source: https://docs.withorb.com/api-reference/invoice/create-a-one-off-invoice /api-reference/orb-openapi.json post /invoices This endpoint is used to create a one-off invoice for a customer. # Create invoice line item Source: https://docs.withorb.com/api-reference/invoice/create-invoice-line-item /api-reference/orb-openapi.json post /invoice_line_items This creates a one-off fixed fee invoice line item on an Invoice. This can only be done for invoices that are in a `draft` status. The behavior depends on which parameters are provided: - If `item_id` is provided without `name`: The item is looked up by ID, and the item's name is used for the line item. - If `name` is provided without `item_id`: An item with the given name is searched for in the account. If found, that item is used. If not found, a new item is created with that name. The new item's name is used for the line item. - If both `item_id` and `name` are provided: The item is looked up by ID for association, but the provided `name` is used for the line item (not the item's name). # Delete invoice line item Source: https://docs.withorb.com/api-reference/invoice/delete-invoice-line-item /api-reference/orb-openapi.json delete /invoices/{invoice_id}/invoice_line_items/{line_item_id} This endpoint deletes an invoice line item from a draft invoice. This endpoint only allows deletion of one-off line items (not subscription-based line items). The invoice must be in a draft status for this operation to succeed. # Fetch invoice Source: https://docs.withorb.com/api-reference/invoice/fetch-invoice /api-reference/orb-openapi.json get /invoices/{invoice_id} This endpoint is used to fetch an [`Invoice`](/core-concepts#invoice) given an identifier. # Fetch upcoming invoice Source: https://docs.withorb.com/api-reference/invoice/fetch-upcoming-invoice /api-reference/orb-openapi.json get /invoices/upcoming This endpoint can be used to fetch the upcoming [invoice](/core-concepts#invoice) for the current billing period given a subscription. # Issue invoice Source: https://docs.withorb.com/api-reference/invoice/issue-invoice /api-reference/orb-openapi.json post /invoices/{invoice_id}/issue This endpoint allows an eligible invoice to be issued manually. This is only possible with invoices where status is `draft`, `will_auto_issue` is false, and an `eligible_to_issue_at` is a time in the past. Issuing an invoice could possibly trigger side effects, some of which could be customer-visible (e.g. sending emails, auto-collecting payment, syncing the invoice to external providers, etc). # Issue invoice summary Source: https://docs.withorb.com/api-reference/invoice/issue-invoice-summary /api-reference/orb-openapi.json post /invoices/summary/{invoice_id}/issue This endpoint allows an eligible invoice to be issued manually. This is only possible with invoices where status is `draft`, `will_auto_issue` is false, and an `eligible_to_issue_at` is a time in the past. Issuing an invoice could possibly trigger side effects, some of which could be customer-visible (e.g. sending emails, auto-collecting payment, syncing the invoice to external providers, etc). This is a lighter-weight alternative to the issue invoice endpoint, returning an invoice summary without any line item details. # List invoices Source: https://docs.withorb.com/api-reference/invoice/list-invoices /api-reference/orb-openapi.json get /invoices This endpoint returns a list of all [`Invoice`](/core-concepts#invoice)s for an account in a list format. The list of invoices is ordered starting from the most recently issued invoice date. The response also includes [`pagination_metadata`](/api-reference/pagination), which lets the caller retrieve the next page of results if they exist. By default, this only returns invoices that are `issued`, `paid`, or `synced`. When fetching any `draft` invoices, this returns the last-computed invoice values for each draft invoice, which may not always be up-to-date since Orb regularly refreshes invoices asynchronously. If you don't need line item details, minimums, maximums, or discounts, prefer the [list invoices summary](/api-reference/invoice/list-invoices-summary) endpoint for better performance. # List invoices summary Source: https://docs.withorb.com/api-reference/invoice/list-invoices-summary /api-reference/orb-openapi.json get /invoices/summary This is a lighter-weight endpoint that returns a list of all [`Invoice`](/core-concepts#invoice) summaries for an account in a list format. These invoice summaries do not include line item details, minimums, maximums, and discounts, making this endpoint more efficient. The list of invoices is ordered starting from the most recently issued invoice date. The response also includes [`pagination_metadata`](/api-reference/pagination), which lets the caller retrieve the next page of results if they exist. By default, this only returns invoices that are `issued`, `paid`, or `synced`. When fetching any `draft` invoices, this returns the last-computed invoice values for each draft invoice, which may not always be up-to-date since Orb regularly refreshes invoices asynchronously. # Mark invoice as paid Source: https://docs.withorb.com/api-reference/invoice/mark-invoice-as-paid /api-reference/orb-openapi.json post /invoices/{invoice_id}/mark_paid This endpoint allows an invoice's status to be set to the `paid` status. This can only be done to invoices that are in the `issued` or `synced` status. # Pay invoice Source: https://docs.withorb.com/api-reference/invoice/pay-invoice /api-reference/orb-openapi.json post /invoices/{invoice_id}/pay This endpoint collects payment for an invoice. By default, it uses the customer's default payment method. Optionally, a shared payment token (SPT) can be provided to pay using agent-granted credentials instead. This action can only be taken on invoices with status "issued". # Regenerate invoice PDF Source: https://docs.withorb.com/api-reference/invoice/regenerate-invoice-pdf /api-reference/orb-openapi.json post /invoices/{invoice_id}/regenerate_invoice_pdf This endpoint triggers a regeneration of the PDF for a finalized invoice. The invoice must be finalized (`issued`, `paid`, `synced`, or `void`) and must already have an existing PDF. The original PDF is archived (not permanently deleted) to maintain an audit trail. **Important Legal Considerations:** Regenerating invoice PDFs may not be permitted in all jurisdictions. Many tax authorities require that issued invoices remain unmodified. Before using this endpoint, ensure that: - Your local tax regulations permit modification of issued billing documents - You have a legitimate business reason (e.g., fixing template errors, updating branding) - You maintain proper records of the original PDF (archived automatically by Orb) Recommended use cases: - Correcting template rendering issues - Applying updated company branding - Updating customer data that was incorrect at issuance # Regenerate receipt PDF Source: https://docs.withorb.com/api-reference/invoice/regenerate-receipt-pdf /api-reference/orb-openapi.json post /invoices/{invoice_id}/regenerate_receipt_pdf This endpoint triggers a regeneration of the receipt PDF for a paid invoice. The invoice must be in `paid` status and must already have an existing receipt PDF. The original PDF is archived (not permanently deleted) to maintain an audit trail. **Important Legal Considerations:** Regenerating receipt PDFs may not be permitted in all jurisdictions. Many tax authorities require that issued receipts remain unmodified. Before using this endpoint, ensure that: - Your local tax regulations permit modification of issued billing documents - You have a legitimate business reason (e.g., fixing template errors, updating branding) - You maintain proper records of the original PDF (archived automatically by Orb) Recommended use cases: - Correcting template rendering issues - Applying updated company branding - Updating customer data that was incorrect at issuance # Update invoice Source: https://docs.withorb.com/api-reference/invoice/update-invoice /api-reference/orb-openapi.json put /invoices/{invoice_id} This endpoint allows you to update the `metadata`, `net_terms`, `due_date`, `invoice_date`, and `auto_collection` properties on an invoice. If you pass null for the metadata value, it will clear any existing metadata for that invoice. `metadata` can be modified regardless of invoice state. `net_terms`, `due_date`, `invoice_date`, and `auto_collection` can only be modified if the invoice is in a `draft` state. `invoice_date` can only be modified for non-subscription invoices. # Void invoice Source: https://docs.withorb.com/api-reference/invoice/void-invoice /api-reference/orb-openapi.json post /invoices/{invoice_id}/void This endpoint allows an invoice's status to be set to the `void` status. This can only be done to invoices that are in the `issued` status. If the associated invoice has used the customer balance to change the amount due, the customer balance operation will be reverted. For example, if the invoice used \$10 of customer balance, that amount will be added back to the customer balance upon voiding. If the invoice was used to purchase a credit block, but the invoice is not yet paid, the credit block will be voided. If the invoice was created due to a top-up, the top-up will be disabled. # Archive item Source: https://docs.withorb.com/api-reference/item/archive-item /api-reference/orb-openapi.json post /items/{item_id}/archive This endpoint archives an item, and cascades to archive every price for that item that is still active. An item that is already archived cannot be archived again. # Create item Source: https://docs.withorb.com/api-reference/item/create-item /api-reference/orb-openapi.json post /items This endpoint is used to create an [Item](/core-concepts#item). # Fetch item Source: https://docs.withorb.com/api-reference/item/fetch-item /api-reference/orb-openapi.json get /items/{item_id} This endpoint returns an item identified by its item_id. # List items Source: https://docs.withorb.com/api-reference/item/list-items /api-reference/orb-openapi.json get /items This endpoint returns a list of all Items, ordered in descending order by creation time. # Update item Source: https://docs.withorb.com/api-reference/item/update-item /api-reference/orb-openapi.json put /items/{item_id} This endpoint can be used to update properties on the Item. # Create a new license for a user Source: https://docs.withorb.com/api-reference/license/create-a-new-license-for-a-user /api-reference/orb-openapi.json post /licenses This endpoint is used to create a new license for a user. If a start date is provided, the license will be activated at the **start** of the specified date in the customer's timezone. Otherwise, the activation time will default to the **start** of the current day in the customer's timezone. # Deactivate license Source: https://docs.withorb.com/api-reference/license/deactivate-license /api-reference/orb-openapi.json post /licenses/{license_id}/deactivate This endpoint is used to deactivate an existing license. If an end date is provided, the license will be deactivated at the **start** of the specified date in the customer's timezone. Otherwise, the deactivation time will default to the **end** of the current day in the customer's timezone. # Fetch license Source: https://docs.withorb.com/api-reference/license/fetch-license /api-reference/orb-openapi.json get /licenses/{license_id} This endpoint is used to fetch a license given an identifier. # Fetch license by external license id Source: https://docs.withorb.com/api-reference/license/fetch-license-by-external-license-id /api-reference/orb-openapi.json get /licenses/external_license_id/{external_license_id} This endpoint is used to fetch a license given an external license identifier. # Get usage for a license by external license ID Source: https://docs.withorb.com/api-reference/license/get-usage-for-a-license-by-external-license-id /api-reference/orb-openapi.json get /licenses/external_licenses/{external_license_id}/usage Returns usage and remaining credits for a license identified by its external license ID. Resolves the currently active license with this external ID, i.e. the license whose active window contains the current time. An external license ID can map to multiple licenses over time, so if none is currently active (deactivated, window ended, or not yet started) this returns a 404. To fetch usage for a license regardless of active state, use the endpoint that takes Orb's internal license ID. Date range defaults to the current billing period if not specified. # Get usage for a specific license Source: https://docs.withorb.com/api-reference/license/get-usage-for-a-specific-license /api-reference/orb-openapi.json get /licenses/{license_id}/usage Returns usage and remaining credits for a specific license over a date range. Resolves the license by ID regardless of whether it is currently active, unlike the external-license-ID variant, which only resolves a currently active license. Date range defaults to the current billing period if not specified. # Get usage for all licenses of a type on a subscription Source: https://docs.withorb.com/api-reference/license/get-usage-for-all-licenses-of-a-type-on-a-subscription /api-reference/orb-openapi.json get /licenses/usage Returns usage and remaining credits for all licenses of a given type on a subscription. Date range defaults to the current billing period if not specified. # List licenses Source: https://docs.withorb.com/api-reference/license/list-licenses /api-reference/orb-openapi.json get /licenses This endpoint returns a list of all licenses for a subscription. # Create license type Source: https://docs.withorb.com/api-reference/licensetype/create-license-type /api-reference/orb-openapi.json post /license_types This endpoint is used to create a new license type. License types are used to group licenses and define billing behavior. Each license type has a name and a grouping key that determines how metrics are aggregated for billing purposes. # Fetch license type Source: https://docs.withorb.com/api-reference/licensetype/fetch-license-type /api-reference/orb-openapi.json get /license_types/{license_type_id} This endpoint returns a license type identified by its license_type_id. Use this endpoint to retrieve details about a specific license type, including its name and grouping key. # List license types Source: https://docs.withorb.com/api-reference/licensetype/list-license-types /api-reference/orb-openapi.json get /license_types This endpoint returns a list of all license types configured for the account, ordered in ascending order by creation time. License types are used to group licenses and define billing behavior. Each license type has a name and a grouping key that determines how metrics are aggregated for billing purposes. # Create metric Source: https://docs.withorb.com/api-reference/metric/create-metric /api-reference/orb-openapi.json post /metrics This endpoint is used to create a [metric](/core-concepts###metric) using a SQL string. See [SQL support](/extensibility/advanced-metrics#sql-support) for a description of constructing SQL queries with examples. # Get metric Source: https://docs.withorb.com/api-reference/metric/get-metric /api-reference/orb-openapi.json get /metrics/{metric_id} This endpoint is used to fetch [metric](/core-concepts#metric) details given a metric identifier. It returns information about the metrics including its name, description, and item. # List metrics Source: https://docs.withorb.com/api-reference/metric/list-metrics /api-reference/orb-openapi.json get /metrics This endpoint is used to list [metrics](/core-concepts#metric). It returns information about the metrics including its name, description, and item. # Update metric Source: https://docs.withorb.com/api-reference/metric/update-metric /api-reference/orb-openapi.json put /metrics/{metric_id} This endpoint allows you to update the `metadata` property on a metric. If you pass `null` for the metadata value, it will clear any existing metadata for that invoice. # Pagination Source: https://docs.withorb.com/api-reference/pagination The Orb API implements cursor-based pagination on endpoints that list resources, such as the [List Customers](/api-reference/customer/list-customers) endpoint. This makes it easy to incrementally load resources in the Orb API without the transfer of excessive amounts of data in one round trip. At a high level, cursor-based pagination employs string tokens in the request and response to indicate reference points in the returned list. ## Paginated API responses List endpoints utilize a standardized format that contains the requested list in a `data` response object, as well as some *metadata* that's useful for pagination in the `pagination_metadata` response object. Generically, the response format is: ```json theme={null} { "data": [ ... ], "pagination_metadata": { "has_more": true, "next_cursor": "..." } } ``` A few notes on this returned result: * `has_more` will be `true` if there are more results that were not returned by this endpoint due to the `limit` value set. * `next_cursor` is a string value that will be non-`null` if `has_more` is `true`. This value should be provided to the same endpoint in a subsequent call to fetch more results, maintaining the same sort order. When `has_more=True`, pass the `next_cursor` response as the `cursor` query parameter to fetch the next page of results. By default, paginated resources are sorted from most recently created to least recently created, determined by the `created_at` field on each resource. ## Limits Orb implements a default page size of 20, with a maximum upper bound of 100 items per request. The number of items requested can be specified by passing in the `limit` query parameter to a paginated request. # Cancel migration Source: https://docs.withorb.com/api-reference/plan/cancel-migration /api-reference/orb-openapi.json post /plans/{plan_id}/migrations/{migration_id}/cancel This endpoint cancels a migration. # Create plan Source: https://docs.withorb.com/api-reference/plan/create-plan /api-reference/orb-openapi.json post /plans This endpoint allows creation of plans including their prices. # Create plan version Source: https://docs.withorb.com/api-reference/plan/create-plan-version /api-reference/orb-openapi.json post /plans/{plan_id}/versions This endpoint allows the creation of a new plan version for an existing plan. # Create plan version by external ID Source: https://docs.withorb.com/api-reference/plan/create-plan-version-by-external-id /api-reference/orb-openapi.json post /plans/external_plan_id/{external_plan_id}/versions This endpoint allows the creation of a new plan version for an existing plan. # Fetch migration Source: https://docs.withorb.com/api-reference/plan/fetch-migration /api-reference/orb-openapi.json get /plans/{plan_id}/migrations/{migration_id} This endpoint returns a migration for a plan, identified by its ID. A migration moves a plan's subscriptions onto a new version of that plan; the response reports the effective time it is scheduled for and its current status. # Fetch plan Source: https://docs.withorb.com/api-reference/plan/fetch-plan /api-reference/orb-openapi.json get /plans/{plan_id} This endpoint is used to fetch [plan](/core-concepts#plan-and-price) details given a plan identifier. It returns information about the prices included in the plan and their configuration, as well as the product that the plan is attached to. ## Serialized prices Orb supports a few different pricing models out of the box. Each of these models is serialized differently in a given [Price](/core-concepts#plan-and-price) object. The `model_type` field determines the key for the configuration object that is present. A detailed explanation of price types can be found in the [Price schema](/core-concepts#plan-and-price). ## Phases Orb supports plan phases, also known as contract ramps. For plans with phases, the serialized prices refer to all prices across all phases. # Fetch plan by external ID Source: https://docs.withorb.com/api-reference/plan/fetch-plan-by-external-id /api-reference/orb-openapi.json get /plans/external_plan_id/{external_plan_id} This endpoint is used to fetch [plan](/core-concepts##plan-and-price) details given an external_plan_id identifier. It returns information about the prices included in the plan and their configuration, as well as the product that the plan is attached to. If multiple plans are found to contain the specified external_plan_id, the active plans will take priority over archived ones, and among those, the endpoint will return the most recently created plan. ## Serialized prices Orb supports a few different pricing models out of the box. Each of these models is serialized differently in a given [Price](/core-concepts#plan-and-price) object. The `model_type` field determines the key for the configuration object that is present. A detailed explanation of price types can be found in the [Price schema](/core-concepts#plan-and-price). " # Fetch plan version Source: https://docs.withorb.com/api-reference/plan/fetch-plan-version /api-reference/orb-openapi.json get /plans/{plan_id}/versions/{version} This endpoint is used to fetch a plan version. It returns the phases, prices, and adjustments present on this version of the plan. # Fetch plan version by external ID Source: https://docs.withorb.com/api-reference/plan/fetch-plan-version-by-external-id /api-reference/orb-openapi.json get /plans/external_plan_id/{external_plan_id}/versions/{version} This endpoint is used to fetch a plan version. It returns the phases, prices, and adjustments present on this version of the plan. # List migrations Source: https://docs.withorb.com/api-reference/plan/list-migrations /api-reference/orb-openapi.json get /plans/{plan_id}/migrations This endpoint returns a list of all migrations for a plan. The list of migrations is ordered starting from the most recently created migration. The response also includes pagination_metadata, which lets the caller retrieve the next page of results if they exist. # List plans Source: https://docs.withorb.com/api-reference/plan/list-plans /api-reference/orb-openapi.json get /plans This endpoint returns a list of all [plans](/core-concepts#plan-and-price) for an account in a list format. The list of plans is ordered starting from the most recently created plan. The response also includes [`pagination_metadata`](/api-reference/pagination) which lets the caller retrieve the next page of results if they exist. # Set plan version as default Source: https://docs.withorb.com/api-reference/plan/set-plan-version-as-default /api-reference/orb-openapi.json post /plans/{plan_id}/set_default_version This endpoint allows setting the default version of a plan. # Set plan version as default by external ID Source: https://docs.withorb.com/api-reference/plan/set-plan-version-as-default-by-external-id /api-reference/orb-openapi.json post /plans/external_plan_id/{external_plan_id}/set_default_version This endpoint allows setting the default version of a plan. # Update plan by external ID Source: https://docs.withorb.com/api-reference/plan/update-plan-by-external-id /api-reference/orb-openapi.json put /plans/external_plan_id/{external_plan_id} This endpoint can be used to update the `external_plan_id`, `description`, and `metadata` of an existing plan. Other fields on a plan are currently immutable. # Update plan by id Source: https://docs.withorb.com/api-reference/plan/update-plan-by-id /api-reference/orb-openapi.json put /plans/{plan_id} This endpoint can be used to update the `external_plan_id`, `description`, and `metadata` of an existing plan. Other fields on a plan are currently immutable. # Add or edit price intervals Source: https://docs.withorb.com/api-reference/price-interval/add-or-edit-price-intervals /api-reference/orb-openapi.json post /subscriptions/{subscription_id}/price_intervals This endpoint is used to add and edit subscription [price intervals](/api-reference/price-interval/add-or-edit-price-intervals). By making modifications to a subscription’s price intervals, you can [flexibly and atomically control the billing behavior of a subscription](/product-catalog/modifying-subscriptions). ## Adding price intervals Prices can be added as price intervals to a subscription by specifying them in the `add` array. A `price_id` or `external_price_id` from an add-on price or previously removed plan price can be specified to reuse an existing price definition (however, please note that prices from other plans cannot be added to the subscription). Additionally, a new price can be specified using the `price` field — this price will be created automatically. A `start_date` must be specified for the price interval. This is the date when the price will start billing on the subscription, so this will notably result in an immediate charge at this time for any billed in advance fixed fees. The `end_date` will default to null, resulting in a price interval that will bill on a continually recurring basis. Both of these dates can be set in the past or the future and Orb will generate or modify invoices to ensure the subscription’s invoicing behavior is correct. Additionally, a discount, minimum, or maximum can be specified on the price interval. This will only apply to this price interval, not any other price intervals on the subscription. ## Adjustment intervals An adjustment interval represents the time period that a particular adjustment (a discount, minimum, or maximum) applies to the prices on a subscription. Adjustment intervals can be added to a subscription by specifying them in the `add_adjustments` array, or modified via the `edit_adjustments` array. When creating an adjustment interval, you'll need to provide the definition of the new adjustment (the type of adjustment, and which prices it applies to), as well as the start and end dates for the adjustment interval. The start and end dates of an existing adjustment interval can be edited via the `edit_adjustments` field (just like price intervals). (To "change" the amount of a discount, minimum, or maximum, then, you'll need to end the existing interval, and create a new adjustment interval with the new amount and a start date that matches the end date of the previous interval.) ## Editing price intervals Price intervals can be adjusted by specifying edits to make in the `edit` array. A `price_interval_id` to edit must be specified — this can be retrieved from the `price_intervals` field on the subscription. A new `start_date` or `end_date` can be specified to change the range of the price interval, which will modify past or future invoices to ensure correctness. If either of these dates are unspecified, they will default to the existing date on the price interval. To remove a price interval entirely from a subscription, set the `end_date` to be equivalent to the `start_date`. ## Fixed fee quantity transitions The fixed fee quantity transitions for a fixed fee price interval can also be specified when adding or editing by passing an array for `fixed_fee_quantity_transitions`. A fixed fee quantity transition must have a `quantity` and an `effective_date`, which is the date after which the new quantity will be used for billing. If a fixed fee quantity transition is scheduled at a billing period boundary, the full quantity will be billed on an invoice with the other prices on the subscription. If the fixed fee quantity transition is scheduled mid-billing period, the difference between the existing quantity and quantity specified in the transition will be prorated for the rest of the billing period and billed immediately, which will generate a new invoice. Notably, the list of fixed fee quantity transitions passed will overwrite the existing fixed fee quantity transitions on the price interval, so the entire list of transitions must be specified to add additional transitions. The existing list of transitions can be retrieved using the `fixed_fee_quantity_transitions` property on a subscription’s serialized price intervals. # Create price Source: https://docs.withorb.com/api-reference/price/create-price /api-reference/orb-openapi.json post /prices This endpoint is used to create a [price](/product-catalog/price-configuration). A price created using this endpoint is always an add-on, meaning that it's not associated with a specific plan and can instead be individually added to subscriptions, including subscriptions on different plans. An `external_price_id` can be optionally specified as an alias to allow ergonomic interaction with prices in the Orb API. See the [Price resource](/product-catalog/price-configuration) for the specification of different price model configurations possible in this endpoint. # Evaluate multiple prices Source: https://docs.withorb.com/api-reference/price/evaluate-multiple-prices /api-reference/orb-openapi.json post /prices/evaluate This endpoint is used to evaluate the output of price(s) for a given customer and time range over ingested events. It enables filtering and grouping the output using [computed properties](/extensibility/advanced-metrics#computed-properties), supporting the following workflows: 1. Showing detailed usage and costs to the end customer. 2. Auditing subtotals on invoice line items. For these workflows, the expressiveness of computed properties in both the filters and grouping is critical. For example, if you'd like to show your customer their usage grouped by hour and another property, you can do so with the following `grouping_keys`: `["hour_floor_timestamp_millis(timestamp_millis)", "my_property"]`. If you'd like to examine a customer's usage for a specific property value, you can do so with the following `filter`: `my_property = 'foo' AND my_other_property = 'bar'`. Prices may either reference existing prices in your Orb account or be defined inline in the request body. Up to 100 prices can be evaluated in a single request. Prices are evaluated on ingested events and the start of the time range must be no more than 100 days ago. To evaluate based off a set of provided events, the [evaluate preview events](/api-reference/price/evaluate-preview-events) endpoint can be used instead. Note that this is a POST endpoint rather than a GET endpoint because it employs a JSON body rather than query parameters. # Evaluate preview events Source: https://docs.withorb.com/api-reference/price/evaluate-preview-events /api-reference/orb-openapi.json post /prices/evaluate_preview_events This endpoint evaluates prices on preview events instead of actual usage, making it ideal for building price calculators and cost estimation tools. You can filter and group results using [computed properties](/extensibility/advanced-metrics#computed-properties) to analyze pricing across different dimensions. Prices may either reference existing prices in your Orb account or be defined inline in the request body. The endpoint has the following limitations: 1. Up to 100 prices can be evaluated in a single request. 2. Up to 500 preview events can be provided in a single request. A top-level customer_id is required to evaluate the preview events. Additionally, all events without a customer_id will have the top-level customer_id added. Note that this is a POST endpoint rather than a GET endpoint because it employs a JSON body rather than query parameters. # Evaluate price Source: https://docs.withorb.com/api-reference/price/evaluate-price /api-reference/orb-openapi.json post /prices/{price_id}/evaluate [NOTE] It is recommended to use the `/v1/prices/evaluate` which offers further functionality, such as multiple prices, inline price definitions, and querying over preview events. This endpoint is used to evaluate the output of a price for a given customer and time range. It enables filtering and grouping the output using [computed properties](/extensibility/advanced-metrics#computed-properties), supporting the following workflows: 1. Showing detailed usage and costs to the end customer. 2. Auditing subtotals on invoice line items. For these workflows, the expressiveness of computed properties in both the filters and grouping is critical. For example, if you'd like to show your customer their usage grouped by hour and another property, you can do so with the following `grouping_keys`: `["hour_floor_timestamp_millis(timestamp_millis)", "my_property"]`. If you'd like to examine a customer's usage for a specific property value, you can do so with the following `filter`: `my_property = 'foo' AND my_other_property = 'bar'`. By default, the start of the time range must be no more than 100 days ago and the length of the results must be no greater than 1000. Note that this is a POST endpoint rather than a GET endpoint because it employs a JSON body rather than query parameters. # Fetch price Source: https://docs.withorb.com/api-reference/price/fetch-price /api-reference/orb-openapi.json get /prices/{price_id} This endpoint returns a price given an identifier. # Fetch price by external price id Source: https://docs.withorb.com/api-reference/price/fetch-price-by-external-price-id /api-reference/orb-openapi.json get /prices/external_price_id/{external_price_id} This endpoint returns a price given an external price id. See the [price creation API](/api-reference/price/create-price) for more information about external price aliases. # List prices Source: https://docs.withorb.com/api-reference/price/list-prices /api-reference/orb-openapi.json get /prices This endpoint is used to list all add-on prices created using the [price creation endpoint](/api-reference/price/create-price). # Update price Source: https://docs.withorb.com/api-reference/price/update-price /api-reference/orb-openapi.json put /prices/{price_id} This endpoint allows you to update the `metadata` property on a price. If you pass null for the metadata value, it will clear any existing metadata for that price. # Update price by external price id Source: https://docs.withorb.com/api-reference/price/update-price-by-external-price-id /api-reference/orb-openapi.json put /prices/external_price_id/{external_price_id} This endpoint allows you to update the `metadata` property on a price. If you pass null for the metadata value, it will clear any existing metadata for that price. # Rate limits Source: https://docs.withorb.com/api-reference/rate-limits The Orb API imposes safeguards to help ensure service availability against bursts of incoming traffic. Orb is implemented in the critical path for many of our customers and our rate limits are designed with that consideration in mind: | Category | Applicable endpoints | Live mode limit | Test mode limit | Trial limit | | :----------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------------------- | :----------------------------- | | Analytics | [Subscription usage](/api-reference/subscription/fetch-subscription-usage), [subscription costs](/api-reference/subscription/fetch-subscription-costs), [customer costs](/api-reference/customer/fetch-customer-costs), and [evaluate price](https://docs.withorb.com/api-reference/price/evaluate-price#evaluate-price) endpoints. | 10 queries per second | 5 queries per second | 1 query per second | | Ledger | [Customer credit balance](/api-reference/credit/fetch-customer-credit-balance), and the [customer credit balance by external ID](/api-reference/credit/fetch-customer-credit-balance-by-external-customer-id) endpoints. | 10 queries per second | 5 queries per second | 1 query per second | | Read-only queries | All `GET` requests exclusive of the `Analytics` scope. | 100 queries per second | 50 queries per second | 10 queries per second | | Write actions | All `POST`, `PUT`, `PATCH`, and `DELETE` requests, excluding the ingest event [endpoint](/api-reference/event/ingest-events). | 50 operations per second | 5 operations per second | 1 operation per second | | Ingestion requests | [Ingest](/api-reference/event/ingest-events) | 100 operations per second | 10 operations per second | 2 operations per second | | Ingestion volume | [Ingest](/api-reference/event/ingest-events) | 10,000 events ingested per minute by default.
This is a default rather than a hard ceiling—the quota can be raised significantly upon request for customers who need higher throughput. For guidance on scaling ingestion well beyond this default, see the guide on [high throughput ingestion](/events-and-metrics/high-throughput-ingestion). | 2,000 events ingested per minute | 100 events ingested per minute | If your workflow requires additional capacity, please reach out to the Orb team to help provision dedicated resources for your workload—Orb routinely raises this limit for customers ingesting at much higher volumes. ## Handling rate limits In the event of a `429 Too many requests` error, we recommend you retry the request with an exponential backoff. # Reliability and scaling Source: https://docs.withorb.com/api-reference/reliability Orb's API is architected for high availability and low latency, and is designed with the following safety measures: 1. For customers with enterprise agreements, dedicated capacity can be provisioned. This ensures that we are able to provide very strict SLAs, and further isolates other accounts from potential load volatility. 2. The API separates out workloads into their own clusters (e.g. ingestion) to ensure that critical actions are given precedence and able to proceed. 3. Orb uses API firewalls to detect anomalous traffic to ensure that clients which are not behaving as intended cannot cause broader side-effects to multi-tenant environments. The following table provides some recommendations on how to approach API failures depending on the category of workload: | Category | Reliability framework | | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Event ingestion | For information about event ingestion throughput, see the guide on [high throughput ingestion](/events-and-metrics/high-throughput-ingestion). For a production integration, Orb highly recommends that you do *not* pass `debug=True` in the ingestion [endpoint](/api-reference/event/ingest-events). Although exceedingly rare, any `5XX` errors should be retried with exponential backoff to ensure event delivery. Note that Orb's [reporting grace period](/events-and-metrics/reporting-errors) allows you to safely send events many hours late, depending on your account's configuration. | | Read-only queries | Read-only queries (such as [invoice preview](/api-reference/invoice/fetch-upcoming-invoice) or [customer costs](/api-reference/customer/fetch-customer-costs)) may be used either in an end-user facing callsite such as your application's usage dashboard or an asynchronous service that continuously exports data. Orb provides an uptime SLA on these queries for enterprise agreements. If you prefer denormalizing this data in your datastore for local access when it's required, consider a service that refreshes these values in the background. If you're not on a provisioned Orb cluster, you may see rare timeouts on these read queries which should be retried. | | Critical write actions | Actions like [creating a subscription](/api-reference/subscription/create-subscription) or a [customer](/api-reference/customer/create-customer) are mission critical and most often are called in the critical path of your application. Orb takes any errors on these endpoints extremely seriously, and also provides strict uptime SLAs for enterprise customers. We recommend you carefully consider whether you should allow users to proceed in the rare event that there are errors on these endpoints, thinking through any implications on data consistency in your data model. | # Apply subscription change Source: https://docs.withorb.com/api-reference/subscription-change/apply-subscription-change /api-reference/orb-openapi.json post /subscription_changes/{subscription_change_id}/apply Apply a subscription change to perform the intended action. If a positive amount is passed with a request to this endpoint, any eligible invoices that were created will be issued immediately if they only contain in-advance fees. # Cancel subscription change Source: https://docs.withorb.com/api-reference/subscription-change/cancel-subscription-change /api-reference/orb-openapi.json post /subscription_changes/{subscription_change_id}/cancel Cancel a subscription change. The change can no longer be applied. A subscription can only have one "pending" change at a time - use this endpoint to cancel an existing change before creating a new one. # Fetch subscription change Source: https://docs.withorb.com/api-reference/subscription-change/fetch-subscription-change /api-reference/orb-openapi.json get /subscription_changes/{subscription_change_id} This endpoint returns a subscription change given an identifier. A subscription change is created by including `Create-Pending-Subscription-Change: True` in the header of a subscription mutation API call (e.g. [create subscription endpoint](/api-reference/subscription/create-subscription), [schedule plan change endpoint](/api-reference/subscription/schedule-plan-change), ...). The subscription change will be referenced by the `pending_subscription_change` field in the response. # List subscription changes Source: https://docs.withorb.com/api-reference/subscription-change/list-subscription-changes /api-reference/orb-openapi.json get /subscription_changes This endpoint returns a list of pending subscription changes for a customer. Use the [Fetch Subscription Change](fetch-subscription-change) endpoint to retrieve the expected subscription state after the pending change is applied. # Cancel subscription Source: https://docs.withorb.com/api-reference/subscription/cancel-subscription /api-reference/orb-openapi.json post /subscriptions/{subscription_id}/cancel This endpoint can be used to cancel an existing subscription. It returns the serialized subscription object with an `end_date` parameter that signifies when the subscription will transition to an ended state. The body parameter `cancel_option` determines the cancellation behavior. Orb supports three cancellation options: - `end_of_subscription_term`: stops the subscription from auto-renewing. Subscriptions that have been cancelled with this option can still incur charges for the remainder of their term: - Issuing this cancellation request for a monthly subscription will keep the subscription active until the start of the subsequent month, and potentially issue an invoice for any usage charges incurred in the intervening period. - Issuing this cancellation request for a quarterly subscription will keep the subscription active until the end of the quarter and potentially issue an invoice for any usage charges incurred in the intervening period. - Issuing this cancellation request for a yearly subscription will keep the subscription active for the full year. For example, a yearly subscription starting on 2021-11-01 and cancelled on 2021-12-08 will remain active until 2022-11-01 and potentially issue charges in the intervening months for any recurring monthly usage charges in its plan. - **Note**: If a subscription's plan contains prices with difference cadences, the end of term date will be determined by the largest cadence value. For example, cancelling end of term for a subscription with a quarterly fixed fee with a monthly usage fee will result in the subscription ending at the end of the quarter. - `immediate`: ends the subscription immediately, setting the `end_date` to the current time: - Subscriptions that have been cancelled with this option will be invoiced immediately. This invoice will include any usage fees incurred in the billing period up to the cancellation, along with any prorated recurring fees for the billing period, if applicable. - **Note**: If the subscription has a recurring fee that was paid in-advance, the prorated amount for the remaining time period will be added to the [customer's balance](list-balance-transactions) upon immediate cancellation. However, if the customer is ineligible to use the customer balance, the subscription cannot be cancelled immediately. - `requested_date`: ends the subscription on a specified date, which requires a `cancellation_date` to be passed in. If no timezone is provided, the customer's timezone is used. For example, a subscription starting on January 1st with a monthly price can be set to be cancelled on the first of any month after January 1st (e.g. March 1st, April 1st, May 1st). A subscription with multiple prices with different cadences defines the "term" to be the highest cadence of the prices. Upcoming subscriptions are only eligible for immediate cancellation, which will set the `end_date` equal to the `start_date` upon cancellation. ## Backdated cancellations Orb allows you to cancel a subscription in the past as long as there are no paid invoices between the `requested_date` and the current time. If the cancellation is after the latest issued invoice, Orb will generate a balance refund for the current period. If the cancellation is before the most recently issued invoice, Orb will void the intervening invoice and generate a new one based on the new dates for the subscription. See the section on [cancellation behaviors](/product-catalog/creating-subscriptions#cancellation-behaviors). # Create subscription Source: https://docs.withorb.com/api-reference/subscription/create-subscription /api-reference/orb-openapi.json post /subscriptions A subscription represents the purchase of a plan by a customer. The customer is identified by either the `customer_id` or the `external_customer_id`, and exactly one of these fields must be provided. By default, subscriptions begin on the day that they're created and renew automatically for each billing cycle at the cadence that's configured in the plan definition. The default configuration for subscriptions in Orb is **In-advance billing** and **Beginning of month alignment** (see [Subscription](/core-concepts##subscription) for more details). In order to change the alignment behavior, Orb also supports billing subscriptions on the day of the month they are created. If `align_billing_with_subscription_start_date = true` is specified, subscriptions have billing cycles that are aligned with their `start_date`. For example, a subscription that begins on January 15th will have a billing cycle from January 15th to February 15th. Every subsequent billing cycle will continue to start and invoice on the 15th. If the "day" value is greater than the number of days in the month, the next billing cycle will start at the end of the month. For example, if the start_date is January 31st, the next billing cycle will start on February 28th. If a customer was created with a currency, Orb only allows subscribing the customer to a plan with a matching `invoicing_currency`. If the customer does not have a currency set, on subscription creation, we set the customer's currency to be the `invoicing_currency` of the plan. ## Customize your customer's subscriptions Prices and adjustments in a plan can be added, removed, or replaced for the subscription being created. This is useful when a customer has prices that differ from the default prices for a specific plan. This feature is only available for accounts that have migrated to Subscription Overrides Version 2. You can find your Subscription Overrides Version at the bottom of your [Plans page](https://app.withorb.com/plans) ### Adding Prices To add prices, provide a list of objects with the key `add_prices`. An object in the list must specify an existing add-on price with a `price_id` or `external_price_id` field, or create a new add-on price by including an object with the key `price`, identical to what would be used in the request body for the [create price endpoint](/api-reference/price/create-price). See the [Price resource](/product-catalog/price-configuration) for the specification of different price model configurations possible in this object. If the plan has phases, each object in the list must include a number with `plan_phase_order` key to indicate which phase the price should be added to. An object in the list can specify an optional `start_date` and optional `end_date`. This is equivalent to creating a price interval with the [add/edit price intervals endpoint](/api-reference/price-interval/add-or-edit-price-intervals). If unspecified, the start or end date of the phase or subscription will be used. An object in the list can specify an optional `minimum_amount`, `maximum_amount`, or `discounts`. This will create adjustments which apply only to this price. Additionally, an object in the list can specify an optional `reference_id`. This ID can be used to reference this price when [adding an adjustment](#adding-adjustments) in the same API call. However the ID is _transient_ and cannot be used to refer to the price in future API calls. ### Removing Prices To remove prices, provide a list of objects with the key `remove_prices`. An object in the list must specify a plan price with either a `price_id` or `external_price_id` field. ### Replacing Prices To replace prices, provide a list of objects with the key `replace_prices`. An object in the list must specify a plan price to replace with the `replaces_price_id` key, and it must specify a price to replace it with by either referencing an existing add-on price with a `price_id` or `external_price_id` field, or by creating a new add-on price by including an object with the key `price`, identical to what would be used in the request body for the [create price endpoint](/api-reference/price/create-price). See the [Price resource](/product-catalog/price-configuration) for the specification of different price model configurations possible in this object. For fixed fees, an object in the list can supply a `fixed_price_quantity` instead of a `price`, `price_id`, or `external_price_id` field. This will update only the quantity for the price, similar to the [Update price quantity](/api-reference/subscription/update-price-quantity) endpoint. The replacement price will have the same phase, if applicable, and the same start and end dates as the price it replaces. An object in the list can specify an optional `minimum_amount`, `maximum_amount`, or `discounts`. This will create adjustments which apply only to this price. Additionally, an object in the list can specify an optional `reference_id`. This ID can be used to reference the replacement price when [adding an adjustment](#adding-adjustments) in the same API call. However the ID is _transient_ and cannot be used to refer to the price in future API calls. ### Adding adjustments To add adjustments, provide a list of objects with the key `add_adjustments`. An object in the list must include an object with the key `adjustment`, identical to the adjustment object in the [add/edit price intervals endpoint](/api-reference/price-interval/add-or-edit-price-intervals). If the plan has phases, each object in the list must include a number with `plan_phase_order` key to indicate which phase the adjustment should be added to. An object in the list can specify an optional `start_date` and optional `end_date`. If unspecified, the start or end date of the phase or subscription will be used. ### Removing adjustments To remove adjustments, provide a list of objects with the key `remove_adjustments`. An object in the list must include a key, `adjustment_id`, with the ID of the adjustment to be removed. ### Replacing adjustments To replace adjustments, provide a list of objects with the key `replace_adjustments`. An object in the list must specify a plan adjustment to replace with the `replaces_adjustment_id` key, and it must specify an adjustment to replace it with by including an object with the key `adjustment`, identical to the adjustment object in the [add/edit price intervals endpoint](/api-reference/price-interval/add-or-edit-price-intervals). The replacement adjustment will have the same phase, if applicable, and the same start and end dates as the adjustment it replaces. ## Price overrides (DEPRECATED) Price overrides are being phased out in favor adding/removing/replacing prices. (See [Customize your customer's subscriptions](/api-reference/subscription/create-subscription)) Price overrides are used to update some or all prices in a plan for the specific subscription being created. This is useful when a new customer has negotiated a rate that is unique to the customer. To override prices, provide a list of objects with the key `price_overrides`. The price object in the list of overrides is expected to contain the existing price id, the `model_type` and configuration. (See the [Price resource](/product-catalog/price-configuration) for the specification of different price model configurations.) The numerical values can be updated, but the billable metric, cadence, type, and name of a price can not be overridden. ### Maximums and Minimums Minimums and maximums, much like price overrides, can be useful when a new customer has negotiated a new or different minimum or maximum spend cap than the default for a given price. If one exists for a price and null is provided for the minimum/maximum override on creation, then there will be no minimum/maximum on the new subscription. If no value is provided, then the default price maximum or minimum is used. To add a minimum for a specific price, add `minimum_amount` to the specific price in the `price_overrides` object. To add a maximum for a specific price, add `maximum_amount` to the specific price in the `price_overrides` object. ### Minimum override example Price minimum override example: ```json { ... "id": "price_id", "model_type": "unit", "unit_config": { "unit_amount": "0.50" }, "minimum_amount": "100.00" ... } ``` Removing an existing minimum example ```json { ... "id": "price_id", "model_type": "unit", "unit_config": { "unit_amount": "0.50" }, "minimum_amount": null ... } ``` ### Discounts Discounts, like price overrides, can be useful when a new customer has negotiated a new or different discount than the default for a price. If a discount exists for a price and a null discount is provided on creation, then there will be no discount on the new subscription. To add a discount for a specific price, add `discount` to the price in the `price_overrides` object. Discount should be a dictionary of the format: ```ts { "discount_type": "amount" | "percentage" | "usage", "amount_discount": string, "percentage_discount": string, "usage_discount": string } ``` where either `amount_discount`, `percentage_discount`, or `usage_discount` is provided. Price discount example ```json { ... "id": "price_id", "model_type": "unit", "unit_config": { "unit_amount": "0.50" }, "discount": {"discount_type": "amount", "amount_discount": "175"}, } ``` Removing an existing discount example ```json { "customer_id": "customer_id", "plan_id": "plan_id", "discount": null, "price_overrides": [ ... ] ... } ``` ## Threshold Billing Orb supports invoicing for a subscription when a preconfigured usage threshold is hit. To enable threshold billing, pass in an `invoicing_threshold`, which is specified in the subscription's invoicing currency, when creating a subscription. E.g. pass in `10.00` to issue an invoice when usage amounts hit \$10.00 for a subscription that invoices in USD. ## Limits By default, Orb limits the number of subscriptions per customer to 100. # Fetch subscription Source: https://docs.withorb.com/api-reference/subscription/fetch-subscription /api-reference/orb-openapi.json get /subscriptions/{subscription_id} This endpoint is used to fetch a [Subscription](/core-concepts##subscription) given an identifier. # Fetch subscription costs Source: https://docs.withorb.com/api-reference/subscription/fetch-subscription-costs /api-reference/orb-openapi.json get /subscriptions/{subscription_id}/costs This endpoint is used to fetch a day-by-day snapshot of a subscription's costs in Orb, calculated by applying pricing information to the underlying usage (see the [subscription usage endpoint](fetch-subscription-usage) to fetch usage per metric, in usage units rather than a currency). The semantics of this endpoint exactly mirror those of [fetching a customer's costs](/api-reference/customer/fetch-customer-costs). Use this endpoint to limit your analysis of costs to a specific subscription for the customer (e.g. to de-aggregate costs when a customer's subscription has started and stopped on the same day). # Fetch subscription schedule Source: https://docs.withorb.com/api-reference/subscription/fetch-subscription-schedule /api-reference/orb-openapi.json get /subscriptions/{subscription_id}/schedule This endpoint returns a [paginated](/api-reference/pagination) list of all plans associated with a subscription along with their start and end dates. This list contains the subscription's initial plan along with past and future plan changes. # Fetch subscription usage Source: https://docs.withorb.com/api-reference/subscription/fetch-subscription-usage /api-reference/orb-openapi.json get /subscriptions/{subscription_id}/usage This endpoint is used to fetch a subscription's usage in Orb. Especially when combined with optional query parameters, this endpoint is a powerful way to build visualizations on top of Orb's event data and metrics. With no query parameters specified, this endpoint returns usage for the subscription's _current billing period_ across each billable metric that participates in the subscription. Usage quantities returned are the result of evaluating the metric definition for the entirety of the customer's billing period. ### Default response shape Orb returns a `data` array with an object corresponding to each billable metric. Nested within this object is a `usage` array which has a `quantity` value and a corresponding `timeframe_start` and `timeframe_end`. The `quantity` value represents the calculated usage value for the billable metric over the specified timeframe (inclusive of the `timeframe_start` timestamp and exclusive of the `timeframe_end` timestamp). Orb will include _every_ window in the response starting from the beginning of the billing period, even when there were no events (and therefore no usage) in the window. This increases the size of the response but prevents the caller from filling in gaps and handling cumbersome time-based logic. The query parameters in this endpoint serve to override this behavior and provide some key functionality, as listed below. Note that this functionality can also be used _in conjunction_ with each other, e.g. to display grouped usage on a custom timeframe. ## Custom timeframe In order to view usage for a custom timeframe rather than the current billing period, specify a `timeframe_start` and `timeframe_end`. This will calculate quantities for usage incurred between timeframe_start (inclusive) and timeframe_end (exclusive), i.e. `[timeframe_start, timeframe_end)`. Note: - These timestamps must be specified in ISO 8601 format and UTC timezone, e.g. `2022-02-01T05:00:00Z`. - Both parameters must be specified if either is specified. ## Grouping by custom attributes In order to view a single metric grouped by a specific _attribute_ that each event is tagged with (e.g. `cluster`), you must additionally specify a `billable_metric_id` and a `group_by` key. The `group_by` key denotes the event property on which to group. When returning grouped usage, only usage for `billable_metric_id` is returned, and a separate object in the `data` array is returned for each value of the `group_by` key present in your events. The `quantity` value is the result of evaluating the billable metric for events filtered to a single value of the `group_by` key. Orb expects that events that match the billable metric will contain values in the `properties` dictionary that correspond to the `group_by` key specified. By default, Orb will not return a `null` group (i.e. events that match the metric but do not have the key set). Currently, it is only possible to view usage grouped by a single attribute at a time. When viewing grouped usage, Orb uses pagination to limit the response size to 1000 groups by default. If there are more groups for a given subscription, pagination metadata in the response can be used to fetch all of the data. The following example shows usage for an "API Requests" billable metric grouped by `region`. Note the extra `metric_group` dictionary in the response, which provides metadata about the group: ```json { "data": [ { "usage": [ { "quantity": 0.19291, "timeframe_start": "2021-10-01T07:00:00Z", "timeframe_end": "2021-10-02T07:00:00Z", }, ... ], "metric_group": { "property_key": "region", "property_value": "asia/pacific" }, "billable_metric": { "id": "Fe9pbpMk86xpwdGB", "name": "API Requests" }, "view_mode": "periodic" }, ... ] } ``` ## Windowed usage The `granularity` parameter can be used to _window_ the usage `quantity` value into periods. When not specified, usage is returned for the entirety of the time range. When `granularity = day` is specified with a timeframe longer than a day, Orb will return a `quantity` value for each full day between `timeframe_start` and `timeframe_end`. Note that the days are demarcated by the _customer's local midnight_. For example, with `timeframe_start = 2022-02-01T05:00:00Z`, `timeframe_end = 2022-02-04T01:00:00Z` and `granularity=day`, the following windows will be returned for a customer in the `America/Los_Angeles` timezone since local midnight is `08:00` UTC: - `[2022-02-01T05:00:00Z, 2022-02-01T08:00:00Z)` - `[2022-02-01T08:00:00, 2022-02-02T08:00:00Z)` - `[2022-02-02T08:00:00, 2022-02-03T08:00:00Z)` - `[2022-02-03T08:00:00, 2022-02-04T01:00:00Z)` ```json { "data": [ { "billable_metric": { "id": "Q8w89wjTtBdejXKsm", "name": "API Requests" }, "usage": [ { "quantity": 0, "timeframe_end": "2022-02-01T08:00:00+00:00", "timeframe_start": "2022-02-01T05:00:00+00:00" }, { "quantity": 0, "timeframe_end": "2022-02-02T08:00:00+00:00", "timeframe_start": "2022-02-01T08:00:00+00:00" }, { "quantity": 0, "timeframe_end": "2022-02-03T08:00:00+00:00", "timeframe_start": "2022-02-02T08:00:00+00:00" }, { "quantity": 0, "timeframe_end": "2022-02-04T01:00:00+00:00", "timeframe_start": "2022-02-03T08:00:00+00:00" } ], "view_mode": "periodic" }, ... ] } ``` ## Decomposable vs. non-decomposable metrics Billable metrics fall into one of two categories: decomposable and non-decomposable. A decomposable billable metric, such as a sum or a count, can be displayed and aggregated across arbitrary timescales. On the other hand, a non-decomposable metric is not meaningful when only a slice of the billing window is considered. As an example, if we have a billable metric that's defined to count unique users, displaying a graph of unique users for each day is not representative of the billable metric value over the month (days could have an overlapping set of 'unique' users). Instead, what's useful for any given day is the number of unique users in the billing period so far, which are the _cumulative_ unique users. Accordingly, this endpoint returns treats these two types of metrics differently when `group_by` is specified: - Decomposable metrics can be grouped by any event property. - Non-decomposable metrics can only be grouped by the corresponding price's invoice grouping key. If no invoice grouping key is present, the metric does not support `group_by`. ## Matrix prices When a billable metric is attached to a price that uses matrix pricing, it's important to view usage grouped by those matrix dimensions. In this case, use the query parameters `first_dimension_key`, `first_dimension_value` and `second_dimension_key`, `second_dimension_value` while filtering to a specific `billable_metric_id`. For example, if your compute metric has a separate unit price (i.e. a matrix pricing model) per `region` and `provider`, your request might provide the following parameters: - `first_dimension_key`: `region` - `first_dimension_value`: `us-east-1` - `second_dimension_key`: `provider` - `second_dimension_value`: `aws` # List subscriptions Source: https://docs.withorb.com/api-reference/subscription/list-subscriptions /api-reference/orb-openapi.json get /subscriptions This endpoint returns a list of all subscriptions for an account as a [paginated](/api-reference/pagination) list, ordered starting from the most recently created subscription. For a full discussion of the subscription resource, see [Subscription](/core-concepts##subscription). Subscriptions can be filtered for a specific customer by using either the customer_id or external_customer_id query parameters. To filter subscriptions for multiple customers, use the customer_id[] or external_customer_id[] query parameters. Subscriptions can be filtered by status using the status query parameter (one of `active`, `ended`, or `upcoming`). To filter for multiple statuses in a single request, use the status[] query parameter, e.g. `status[]=active&status[]=ended`. # Redeem Coupon Source: https://docs.withorb.com/api-reference/subscription/redeem-coupon /api-reference/orb-openapi.json post /subscriptions/{subscription_id}/redeem_coupon Redeem a coupon effective at a given time. # Schedule plan change Source: https://docs.withorb.com/api-reference/subscription/schedule-plan-change /api-reference/orb-openapi.json post /subscriptions/{subscription_id}/schedule_plan_change This endpoint can be used to change an existing subscription's plan. It returns the serialized updated subscription object. The body parameter `change_option` determines when the plan change occurs. Orb supports three options: - `end_of_subscription_term`: changes the plan at the end of the existing plan's term. - Issuing this plan change request for a monthly subscription will keep the existing plan active until the start of the subsequent month. Issuing this plan change request for a yearly subscription will keep the existing plan active for the full year. Charges incurred in the remaining period will be invoiced as normal. - Example: The plan is billed monthly on the 1st of the month, the request is made on January 15th, so the plan will be changed on February 1st, and invoice will be issued on February 1st for the last month of the original plan. - `immediate`: changes the plan immediately. - Subscriptions that have their plan changed with this option will move to the new plan immediately, and be invoiced immediately. - This invoice will include any usage fees incurred in the billing period up to the change, along with any prorated recurring fees for the billing period, if applicable. - Example: The plan is billed monthly on the 1st of the month, the request is made on January 15th, so the plan will be changed on January 15th, and an invoice will be issued for the partial month, from January 1 to January 15, on the original plan. - `requested_date`: changes the plan on the requested date (`change_date`). - If no timezone is provided, the customer's timezone is used. The `change_date` body parameter is required if this option is chosen. - Example: The plan is billed monthly on the 1st of the month, the request is made on January 15th, with a requested `change_date` of February 15th, so the plan will be changed on February 15th, and invoices will be issued on February 1st and February 15th. Note that one of `plan_id` or `external_plan_id` is required in the request body for this operation. ## Interaction with scheduled cancellations Scheduling a plan change also unschedules a pending cancellation, as long as the cancellation date is on or after the plan change date: Orb honors the plan change over the scheduled cancellation, and the subscription continues on the new plan. Scheduling a plan change after the subscription's end date returns a validation error instead. Any plan changes already scheduled at or after the new plan change's date are unscheduled and replaced by it. ## Customize your customer's subscriptions Prices and adjustments in a plan can be added, removed, or replaced on the subscription when you schedule the plan change. This is useful when a customer has prices that differ from the default prices for a specific plan. This feature is only available for accounts that have migrated to Subscription Overrides Version 2. You can find your Subscription Overrides Version at the bottom of your [Plans page](https://app.withorb.com/plans) ### Adding Prices To add prices, provide a list of objects with the key `add_prices`. An object in the list must specify an existing add-on price with a `price_id` or `external_price_id` field, or create a new add-on price by including an object with the key `price`, identical to what would be used in the request body for the [create price endpoint](/api-reference/price/create-price). See the [Price resource](/product-catalog/price-configuration) for the specification of different price model configurations possible in this object. If the plan has phases, each object in the list must include a number with `plan_phase_order` key to indicate which phase the price should be added to. An object in the list can specify an optional `start_date` and optional `end_date`. If `start_date` is unspecified, the start of the phase / plan change time will be used. If `end_date` is unspecified, it will finish at the end of the phase / have no end time. An object in the list can specify an optional `minimum_amount`, `maximum_amount`, or `discounts`. This will create adjustments which apply only to this price. Additionally, an object in the list can specify an optional `reference_id`. This ID can be used to reference this price when [adding an adjustment](#adding-adjustments) in the same API call. However the ID is _transient_ and cannot be used to refer to the price in future API calls. ### Removing Prices To remove prices, provide a list of objects with the key `remove_prices`. An object in the list must specify a plan price with either a `price_id` or `external_price_id` field. ### Replacing Prices To replace prices, provide a list of objects with the key `replace_prices`. An object in the list must specify a plan price to replace with the `replaces_price_id` key, and it must specify a price to replace it with by either referencing an existing add-on price with a `price_id` or `external_price_id` field, or by creating a new add-on price by including an object with the key `price`, identical to what would be used in the request body for the [create price endpoint](/api-reference/price/create-price). See the [Price resource](/product-catalog/price-configuration) for the specification of different price model configurations possible in this object. For fixed fees, an object in the list can supply a `fixed_price_quantity` instead of a `price`, `price_id`, or `external_price_id` field. This will update only the quantity for the price, similar to the [Update price quantity](/api-reference/subscription/update-price-quantity) endpoint. The replacement price will have the same phase, if applicable, and the same start and end dates as the price it replaces. An object in the list can specify an optional `minimum_amount`, `maximum_amount`, or `discounts`. This will create adjustments which apply only to this price. Additionally, an object in the list can specify an optional `reference_id`. This ID can be used to reference the replacement price when [adding an adjustment](#adding-adjustments) in the same API call. However the ID is _transient_ and cannot be used to refer to the price in future API calls. ### Adding adjustments To add adjustments, provide a list of objects with the key `add_adjustments`. An object in the list must include an object with the key `adjustment`, identical to the adjustment object in the [add/edit price intervals endpoint](/api-reference/price-interval/add-or-edit-price-intervals). If the plan has phases, each object in the list must include a number with `plan_phase_order` key to indicate which phase the adjustment should be added to. An object in the list can specify an optional `start_date` and optional `end_date`. If `start_date` is unspecified, the start of the phase / plan change time will be used. If `end_date` is unspecified, it will finish at the end of the phase / have no end time. ### Removing adjustments To remove adjustments, provide a list of objects with the key `remove_adjustments`. An object in the list must include a key, `adjustment_id`, with the ID of the adjustment to be removed. ### Replacing adjustments To replace adjustments, provide a list of objects with the key `replace_adjustments`. An object in the list must specify a plan adjustment to replace with the `replaces_adjustment_id` key, and it must specify an adjustment to replace it with by including an object with the key `adjustment`, identical to the adjustment object in the [add/edit price intervals endpoint](/api-reference/price-interval/add-or-edit-price-intervals). The replacement adjustment will have the same phase, if applicable, and the same start and end dates as the adjustment it replaces. ## Price overrides (DEPRECATED) Price overrides are being phased out in favor adding/removing/replacing prices. (See [Customize your customer's subscriptions](/api-reference/subscription/schedule-plan-change)) Price overrides are used to update some or all prices in a plan for the specific subscription being created. This is useful when a new customer has negotiated a rate that is unique to the customer. To override prices, provide a list of objects with the key `price_overrides`. The price object in the list of overrides is expected to contain the existing price id, the `model_type` and configuration. (See the [Price resource](/product-catalog/price-configuration) for the specification of different price model configurations.) The numerical values can be updated, but the billable metric, cadence, type, and name of a price can not be overridden. ### Maximums, and minimums Price overrides are used to update some or all prices in the target plan. Minimums and maximums, much like price overrides, can be useful when a new customer has negotiated a new or different minimum or maximum spend cap than the default for the plan. The request format for maximums and minimums is the same as those in [subscription creation](create-subscription). ## Scheduling multiple plan changes When scheduling multiple plan changes with the same date, the latest plan change on that day takes effect. ## Prorations for in-advance fees By default, Orb calculates the prorated difference in any fixed fees when making a plan change, adjusting the customer balance as needed. For details on this behavior, see [Modifying subscriptions](/product-catalog/modifying-subscriptions#prorations-for-in-advance-fees). # Trigger phase Source: https://docs.withorb.com/api-reference/subscription/trigger-phase /api-reference/orb-openapi.json post /subscriptions/{subscription_id}/trigger_phase Manually trigger a phase, effective the given date (or the current time, if not specified). # Unschedule fixed fee quantity updates Source: https://docs.withorb.com/api-reference/subscription/unschedule-fixed-fee-quantity-updates /api-reference/orb-openapi.json post /subscriptions/{subscription_id}/unschedule_fixed_fee_quantity_updates This endpoint can be used to clear scheduled updates to the quantity for a fixed fee. If there are no updates scheduled, a request validation error will be returned with a 400 status code. # Unschedule plan change Source: https://docs.withorb.com/api-reference/subscription/unschedule-plan-change /api-reference/orb-openapi.json post /subscriptions/{subscription_id}/unschedule_pending_plan_changes This endpoint can be used to unschedule any pending plan changes on an existing subscription. When called, all upcoming plan changes will be unscheduled; it is not possible to unschedule a single plan change if multiple are scheduled. Note: unscheduling a plan change is a lossy operation, with the same semantics as [unscheduling a cancellation](/api-reference/subscription/unschedule-subscription-cancellation). Prices and adjustments on the current plan that were scheduled to end at the plan change time are extended to infinity (original end dates are lost), and anything scheduled to start after the plan change time is permanently deleted. Coupons redeemed as part of an unscheduled plan change are released and can be redeemed again. A scheduled cancellation is not affected by this operation: if the subscription has both a pending plan change and a scheduled cancellation, unscheduling the plan change leaves the cancellation in place. # Unschedule subscription cancellation Source: https://docs.withorb.com/api-reference/subscription/unschedule-subscription-cancellation /api-reference/orb-openapi.json post /subscriptions/{subscription_id}/unschedule_cancellation This endpoint can be used to unschedule any pending cancellations for a subscription. To be eligible, the subscription must currently be active and have a future cancellation. This operation will turn on auto-renew, ensuring that the subscription does not end at the currently scheduled cancellation time. A cancellation that has already taken effect cannot be unscheduled. This includes backdated cancellations: once the subscription's end date is in the past, this endpoint returns a validation error. Note: uncancellation is a lossy operation. Price and adjustment intervals that were cut short by the cancellation are extended to infinity (original end dates are lost), and future intervals or phases scheduled after the cancellation time are permanently deleted. For complex subscriptions with phases or scheduled plan changes, consider creating a new plan change instead of uncancelling. If the scheduled cancellation already produced invoices or credit notes (for example, a proration refund credit note for an in-advance fee), uncancelling voids and reissues them as needed to match the subscription's new state. # Update price quantity Source: https://docs.withorb.com/api-reference/subscription/update-price-quantity /api-reference/orb-openapi.json post /subscriptions/{subscription_id}/update_fixed_fee_quantity This endpoint can be used to update the quantity for a fixed fee. To be eligible, the subscription must currently be active and the price specified must be a fixed fee (not usage-based). This operation will immediately update the quantity for the fee, or if a `effective_date` is passed in, will update the quantity on the requested date at midnight in the customer's timezone. In order to change the fixed fee quantity as of the next draft invoice for this subscription, pass `change_option=upcoming_invoice` without an `effective_date` specified. If the fee is an in-advance fixed fee, it will also issue an immediate invoice for the difference for the remainder of the billing period. # Update subscription Source: https://docs.withorb.com/api-reference/subscription/update-subscription /api-reference/orb-openapi.json put /subscriptions/{subscription_id} This endpoint can be used to update the `metadata`, `net terms`, `auto_collection`, `invoicing_threshold`, and `default_invoice_memo` properties on a subscription. # Update trial for subscription Source: https://docs.withorb.com/api-reference/subscription/update-trial-for-subscription /api-reference/orb-openapi.json post /subscriptions/{subscription_id}/update_trial This endpoint is used to update the trial end date for a subscription. The new trial end date must be within the time range of the current plan (i.e. the new trial end date must be on or after the subscription's start date on the current plan, and on or before the subscription end date). In order to retroactively remove a trial completely, the end date can be set to the transition date of the subscription to this plan (or, if this is the first plan for this subscription, the subscription's start date). In order to end a trial immediately, the keyword `immediate` can be provided as the trial end date. By default, Orb will shift only the trial end date (and price intervals that start or end on the previous trial end date), and leave all other future price intervals untouched. If the `shift` parameter is set to `true`, Orb will shift all subsequent price and adjustment intervals by the same amount as the trial end date shift (so, e.g., if a plan change is scheduled or an add-on price was added, that change will be pushed back by the same amount of time the trial is extended). # Diff-based engine Source: https://docs.withorb.com/architecture/billing-architecture Orb's diff-based billing engine enables backdated changes, safe migrations, and atomic previews—capabilities that traditional billing systems cannot support. This architecture ensures billing modifications are reversible, auditable, and always correct. *** ## The problem with traditional billing Most billing systems assume changes happen in the future. You schedule a plan change for next month. You add an add-on starting tomorrow. Everything flows forward in time. But real business doesn't work that way: * A sales rep closes a deal on Monday but the contract was signed Friday * A customer calls to complain and you need to backdate a discount * You discover a pricing error from two weeks ago that affected 50 customers * An enterprise contract requires a commitment that should have started last quarter Traditional systems handle these cases poorly—or not at all. Engineers write one-off scripts. Finance creates manual adjustments. Data integrity suffers. Orb's diff-based engine treats every change as a comparison between "what should exist" and "what currently exists," then applies only the difference. Past, present, or future—the same mechanism handles all cases. ## Principles for safe subscription management Orb's architecture is grounded in two core principles: 1. **Explicit timing for all actions** * Every action—whether it's a usage event, subscription edit, or cancellation—is attached to an explicit timestamp, not just the "current time." * This prevents subtle errors, especially around billing period boundaries. For example, canceling a subscription at the end of the month should not risk overcharging a customer just because the request crossed a midnight boundary. * With Orb, you always control the effective time of every action, ensuring outcomes match your intent. 2. **Reversibility and backdating** * All actions are reversible whenever possible. You can undo a scheduled cancellation, extend a trial, or change a contract ramp at any time—unless there are end-user-facing side effects (like a sent invoice or email), in which case Orb issues credit notes to correct the record. * Orb is built to support backdated actions: the system "rewinds time" as if the action happened at the specified date, then automatically plays forward the consequences to catch up to the present. ## How the diff engine works When you make any change to a subscription—add a price, apply a discount, backdate a cancellation—Orb doesn't directly manipulate invoices. Instead: 1. **Compute expected state**: Given the subscription parameters (prices, timing, configuration), what invoices *should* exist? 2. **Compare to actual state**: What invoices currently exist in the database? 3. **Calculate the diff**: What needs to be created, modified, or removed? 4. **Apply atomically**: Create new invoices, void or credit existing ones, update the ledger—all in a single transaction The engine computes an `InvoiceDiff` containing: * Invoices to create (with full line item details) * Invoices to remove (draft invoices are deleted; issued invoices are voided or refunded) * Invoices to edit (for in-place modifications to draft invoices) * Credit notes to issue (for refunding portions of paid invoices) * Ledger entries to adjust (for prepaid credit rebalancing) The diff is validated, then applied atomically. If anything fails, nothing changes. ## Why this matters: real-world scenarios ### Backdated subscription creation A customer signs a contract on March 15th, but their service started March 1st. You create the subscription with a start date of March 1st. What happens: 1. Orb computes what invoices should exist for March 1-15 2. If usage occurred during that period (events with timestamps in that range), it's automatically included 3. Invoices are created with the correct service periods 4. Revenue recognition reflects the actual service dates No scripts. No manual adjustments. The subscription exists as if it had been created on time. ### Mid-period cancellation with refund A customer cancels on March 20th, but they paid for the full month upfront. What happens: 1. Orb computes the expected state: subscription ends March 20th 2. The diff shows: existing invoice overstates the service period 3. Orb issues a credit note for the unused portion (March 21-31) 4. The credit goes to customer balance for future invoices (or refund if configured) The original invoice remains unchanged for audit purposes. The credit note records the adjustment. ### Pricing error affecting multiple customers You discover that 50 customers were on the wrong plan for the past two weeks. What happens: 1. You correct each subscription's pricing (or run a migration) 2. For each customer, Orb computes the diff between old and new pricing 3. Draft invoices are updated in place 4. Issued invoices get credit notes or voids as appropriate 5. Revenue reports automatically reflect the corrections Same mechanism, applied 50 times. No special handling required. ## Atomicity enables previews, dry runs, and pending changes A unique advantage of Orb's atomic, diff-based architecture is that every change is a pure, replayable calculation. This means: * Previews of any endpoint are possible: Because the system can deterministically simulate the effect of any change without side effects, you can preview the result of any API call—whether it's a subscription mutation, pricing update, or usage event—before committing it. * Dry runs: Orb's [dry run feature](/essentials/dry-run) lets you validate requests and see their impact without making any actual changes. The system computes the outcome as if the change were real, but nothing is persisted. * Pending changes: With [pending changes](/essentials/pending-changes), you can stage a subscription change, preview its effects (including invoice previews), and only apply it when you're ready. This is possible because Orb can always calculate the post-change state without mutating the underlying data until you commit. This atomicity and replayability are only possible because Orb never mutates state as a side effect of an API call. Instead, every change is a transaction that can be previewed, staged, or rolled back, giving you full control and confidence in your billing operations. ## Versioned plans and safe, scalable migrations Orb's architecture also enables first-class support for plan versioning and bulk migrations—capabilities that are difficult in traditional billing systems. * Versioned timelines: Every subscription in Orb is a timeline of price objects, each with a start and (optionally) end date. When a plan changes—such as adding an add-on or rolling out new pricing—a new version is created, and the system tracks the lineage and differences between versions. * Immutable auditability: Plans themselves are immutable; instead, new versions are layered on, ensuring a clean audit trail for every change. This means you always know exactly how each invoice was generated, and can trace changes over time. * Atomic invoice regeneration: When a subscription migrates to a new plan version, Orb atomically re-generates the set of invoices that should exist, based on the new timeline. This is the same diff-based mechanism that powers all other changes, ensuring migrations are safe and idempotent. * Flexible effective dates: Migrations can be scheduled to take effect at any date—immediately, at a specific future date, or aligned to each customer's next billing cycle. Orb's primitives allow maximum flexibility, so you can roll out pricing changes exactly when and how you want. * Immediate, scalable propagation: All migrations are applied immediately to the subscription's state, regardless of the effective date. This ensures that upcoming invoices always reflect the correct pricing, and previews are accurate. Even for large-scale migrations across millions of subscriptions, Orb guarantees correctness and consistency. This approach eliminates the need for risky migration scripts, manual data dumps, or overnight batch jobs. Instead, migrations are safe, auditable, and fully integrated into the billing engine's atomic, diff-based model. ## What this guarantees The diff-based architecture provides concrete guarantees that traditional billing systems cannot match: | Guarantee | How it works | | ------------------------- | -------------------------------------------------------------------------------------------------------------------- | | **No orphaned invoices** | Every invoice exists because the subscription state demands it. Change the state, and invoices update automatically. | | **No missing revenue** | Usage events are never "lost"—they're always included in the invoice for their timestamp's billing period. | | **No double-billing** | The diff explicitly handles what to remove. You can't accidentally create duplicate charges. | | **Audit trail preserved** | Voided invoices and credit notes remain in the system. The full history is always available. | | **Atomic consistency** | Either all changes apply, or none do. No partial states, no cleanup required. | These aren't aspirational goals. They're consequences of the architecture. The diff-based approach makes entire classes of billing errors structurally impossible. # Query-based billing Source: https://docs.withorb.com/architecture/query-based-billing Orb's billing engine treats billing as the output of a deterministic query, not a side effect of streaming state mutations. This architectural choice eliminates entire classes of billing errors and enables capabilities impossible in traditional systems. *** ## The fundamental difference Most billing systems process usage events as they arrive, incrementing counters and updating aggregations in real-time. This seems efficient, but it creates a fundamental problem: once an event is processed and the counter is incremented, the original context is lost. If pricing changes, if events arrive late, or if a mistake needs correction—there's no safe way to recalculate. Orb takes a different approach. Every usage event is stored immutably in a columnar data store. When generating an invoice, Orb executes a fresh query over the raw event data, applying the current metric definitions and pricing configuration. The invoice is computed, not retrieved. This means: * **The same query, run twice, produces the same invoice**—true determinism * **Late-arriving events don't corrupt existing state**—re-query to get the correct answer * **Pricing changes can be applied retroactively**—the historical events are still there * **Every line item is traceable to specific events**—complete auditability By decoupling invoicing from ingest‑time side effects, Orb eliminates errors such as double‑counting, drift between counters and source data, and silent corruption. ## Core concept: billing as a query Billing is treated as a pure function. Usage events are stored immutably. When generating an invoice, Orb queries: > "Given this customer, this billing period, and this subscription and pricing configuration, what should the invoice look like?" Invoices are derived from: * **Raw usage events:** All usage data is stored immutably and forms the foundation for invoice calculation. * **Immutable metric definitions:** Metrics themselves are immutable. If usage events need to be amended, Orb applies amendments as a read-path overlay, layering corrections or changes on top of the original events to calculate the correct set of events to honor. * **Versioned pricing objects:** Pricing changes are tracked over time, supporting accurate billing for any period. * **Versioned subscription timelines:** Subscription changes are recorded as timelines, allowing for precise reconstruction of billing states. This model provides the following benefits: * **Deterministic results:** The same inputs always yield the same output, ensuring consistency and predictability. * **Full auditability:** Every line item is traceable to its underlying usage and configuration, supporting robust audits. * **Safe backfilling:** Delayed data or configuration fixes can be re-run safely without risk of corruption. * **Simulation capability:** Pricing changes or plan migrations can be tested before being applied, enabling safe experimentation. * **Scalability:** Heavy computation is performed offline, so production traffic is not impacted by expensive aggregations. ## Just-in-time invalidation and invoice computation Orb does not run full-period queries only at invoicing time. Instead, a dependency graph and invalidation system track how each invoice fragment depends on usage, pricing, and configuration. When relevant data changes—such as a late-arriving usage event, plan migration, or pricing update—only the affected invoice fragments are invalidated. When an invoice is generated, Orb computes only the invoices and fragments that are out of date, based on the latest state. This approach provides: * **Efficiency:** Only the minimal set of invoices are recomputed, even as data changes across millions of subscriptions. * **Correctness:** Every invoice reflects the latest, correct state, regardless of the number of changes or corrections. * **Safety:** The system is idempotent and auditable, with every change traceable and reversible. This invalidation and dependency-tracking mechanism enables high-scale, real-time billing operations without sacrificing accuracy or auditability. ## Why this matters — real‑world impact Most billing platforms mutate counters when events are ingested. This makes backfilling difficult and corrections risky. Orb's architecture avoids these problems by not relying on pre-aggregated counters. If pricing changes, usage is delayed, or a subscription is misconfigured, Orb allows the inputs to be fixed and billing to be re-run. This is not possible in stream-mutation systems without manual intervention. ### For finance teams * **Month-end close in hours, not days**: When you close an accounting period, the numbers are final. If billing activity occurs for a closed period, Orb automatically applies catch-up adjustments to the next open period—no manual journal entries. * **Every number is auditable**: Drill from a revenue line item down to the specific events that generated it. No black-box calculations. * **Corrections without fear**: Issuing a credit note or voiding an invoice doesn't corrupt downstream reports. The system recalculates from source data. ### For engineering teams * **Safe backfilling**: Discovered that events from three days ago were missing? Backfill them. The invoices recalculate correctly. * **No migration scripts**: Pricing changes don't require data migrations. The new pricing logic applies to historical events automatically. * **Testable billing logic**: Preview any invoice before it's issued. Simulate pricing changes against historical data. Know exactly what will happen. ### For product teams * **Iterate on pricing**: Test new pricing models against real usage data before rolling them out. See the revenue impact for every customer. * **Launch faster**: New billable metrics can be defined retroactively—no need to instrument first and wait for data to accumulate. ## Real-time without compromise A common objection: "If you're querying raw events, how can you support real-time use cases?" Orb runs two parallel systems: | System | Purpose | Latency | Flexibility | | ------------------------- | ----------------------------------------------- | ------- | -------------------------------- | | **Query-based billing** | Invoice generation, revenue reporting | Minutes | Full SQL expressiveness | | **Stream-based alerting** | Usage alerts, threshold invoices, rate limiting | Seconds | Incrementally computable metrics | The stream-based system maintains running totals in Redis (MemoryDB) as events arrive. When a threshold is crossed, webhooks fire immediately. But the billing logic never touches these counters—invoices are always computed from raw events. This dual architecture means you get both: * **Real-time operational signals** for product experiences * **Accurate, auditable invoices** that can be reconstructed from first principles ### How the dual pipeline works 1. **Event arrives** via the ingestion API 2. **Stream pipeline** updates cached metric values in Redis, checks alert thresholds 3. **If threshold crossed**, webhook fires within seconds 4. **Raw event** is written to the columnar store 5. **At invoice time**, billing queries the columnar store directly—never the cached values The cached values are an optimization for speed. The raw events are the source of truth for accuracy. ### Scale and performance * **Event ingestion**: Ingesting events directly via the Orb API comfortably supports well into the thousands of events per second, with a [cloud storage bucket sync](/events-and-metrics/cloud-storage-integration) recommended as a best practice for added resiliency at higher volumes, and [hosted rollups](/events-and-metrics/high-throughput-ingestion) available as a separate product for extreme-scale, real-time rollup aggregation * **Alert latency**: Seconds from event to webhook for incrementally computable metrics ([SUM, COUNT](/usage-alerting/overview)) * **Invoice computation**: Heavy computation runs asynchronously, never blocking your API calls * **Tested at scale**: Field-tested with companies processing millions of events per second ### Raw data, high‑throughput analytics All usage events are stored in a columnar OLAP store. This provides: * **High throughput:** Production clusters sustain much higher throughput than typical OLAP stores. * **Flexible analytics:** Query raw events via Orb's SQL‑powered API to break down usage by any property. * **Retroactive metrics:** Define a new metric and calculate it back to day zero without re‑ingesting data. * **Simulation:** The same raw dataset powers [Orb Simulations](/simulations/introduction), replaying historical events through hypothetical pricing. Because the source of truth is immutable event history—not lossy counters—flexibility is not traded for scale. ## The bottom line Traditional billing systems make trade-offs: you get either speed or accuracy, either flexibility or scale. Orb's architecture refuses these trade-offs. | What you need | How Orb delivers | | --------------------------- | ---------------------------------------------------------------------- | | **Accurate invoices** | Every invoice is computed from raw events—no accumulated drift | | **Fast alerts** | Parallel streaming pipeline for real-time thresholds | | **Retroactive corrections** | Re-query historical events with corrected logic | | **Safe migrations** | No data migrations required—change the pricing and recalculate | | **Complete audit trail** | Every line item traces to specific events | | **Scale** | Columnar storage and incremental computation handle millions of events | This is the foundation for billing you can trust: reproducible calculations, immutable history, and the flexibility to evolve your pricing without fear. # Core concepts Source: https://docs.withorb.com/core-concepts Orb's query-based architecture is built on core entities that form your billing data model. This guide defines each entity, explains its role in the billing system, and links to relevant documentation for implementation. ## Account Every Orb resource lives within an Account. A single user of Orb (any person with access to the product) by default has access to *two* accounts, a **live** mode and a **test** mode. Note that other than sharing a user login, these environments are completely isolated, and you will need to use separate API credentials to access each account. In some cases, it's possible to automatically copy objects between accounts to "promote" a configuration in test mode to live mode. The test mode account cannot be connected to a production payment processor, which prevents unintended money movement in non-production scenarios. The test mode account *can* still send emails to your users when triggered manually, in order to enable end to end testing of Orb. In addition to using Orb's test mode for *manual* testing, consider integrating the test mode environment with your CI/CD pipeline for automated integration testing with Orb. There are a few critical settings configured per account. In addition to those configurable on the [account settings page](https://app.withorb.com/settings) directly, the following settings are configured during our onboarding process: * Ingestion grace period: Determines how far the `timestamp` that an event is labeled with can lag the current wall time (usually 12 hours). As a side-effect, this is the minimum amount of time that Orb waits before issuing an end of period invoice that contains usage. * Account logo: Determines the custom branding treatment for your invoices and end-user facing links. * Timezone: Your account-wide timezone determines default billing behavior; see [timezones](/essentials/timezones) for a more detailed discussion. ## Customer A Customer is a representation of a business or user that you sell to. Orb generally does not have personally identifying information about your customers, other than a name and email address. A Customer is the basis for all billing in Orb, and is the entity that has events, subscriptions, invoices, and payments associated with it. Orb recommends setting the `external_customer_id` field on every Orb customer to a stable ID in your system, such as your customer's primary key. This allows you to avoid denormalizing any Orb Customer IDs in your datastore, and makes both event reporting and resource management easier. ### Customer deletion: Test mode vs Live mode Orb allows different customer deletion behaviors depending on whether you are operating in test mode or live mode: **Test mode:** * Customers can be deleted even if they have issued invoices * This provides flexibility for testing and development scenarios where you may need to clean up test data * All associated subscriptions and invoices are also deleted as part of the customer deletion process **Live mode:** * Customers with issued invoices cannot be deleted to maintain data integrity and audit compliance * This restriction helps ensure accurate financial records and prevents accidental loss of billing history * Only customers without any issued invoices can be deleted in live mode The test mode environment is completely isolated from live mode, so any customer deletion in test mode will not affect your production data or financial records. | Resource | Description | | ----------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [Customer API reference](/api-reference/customer/create-customer) | Used to create, update, and fetch customers in Orb. If you have a self-serve business where users can sign up for service without your intervention, you should use this API to create a customer in Orb every time a user signs up. | | [Customer ID aliases](/events-and-metrics/customer-aliases) | Use customer aliases for more ergonomic interaction with Orb's customer APIs. | ## Event An Event is a business-specific piece of data that you send to Orb. Depending on your business, the exact shape of your event stream may vary; events will form the basis of any usage-based prices that you charge your customers on. Events are meant to be the *raw data* that your application emits, rather than the change in a specific billable value; this gives you the flexibility to integrate your event stream once and evolve the metrics you construct over these events over time as requirements change. For example, if you're tracking the number of monthly active users in your application, you should send Orb an event for each login (or perhaps even user activity) rather than the updated count of active users based your own aggregate summaries. Events are the foundation of Orb's query-based architecture. Because events are stored immutably, metrics can be defined or changed at any time, and every charge is traceable to its source events—providing complete auditability. | Resource | Description | | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Ingestion quickstart](/quickstart/ingest) | Takes you through some simple API commands so you can familiarize yourself with sending events to the API. | | [Ingestion guide](/events-and-metrics/event-ingestion) | The developer guide includes examples of domain-specific events, how to determine your customized event schema, and the various ingestion integrations that Orb supports. | | [Batch ingestion API](/api-reference/event/ingest-events) | A key part of any Orb integration, used to send events to the Orb platform. Orb is responsible for storing and appropriately indexing your event data. | | [Events backfill API](/api-reference/event/create-backfill) | Event backfills support overwriting previously ingested events in an audit-friendly way. | ## Metric A metric, also known as a "billable metric", is a query over ingested events that outputs an aggregate value. This aggregate value (e.g. a `COUNT` or a `SUM`) can then be tied to a pricing model. Conceptually, think of a metric as a materialized view that Orb is responsible for keeping up to date for the relevant set of customers and billing periods; a metric doesn't need to contain any customer or time specific context to operate. Metrics are queries, not pre-aggregated counters. This distinction is fundamental to Orb's query-based architecture: metric definitions can be changed or applied to historical usage without re-ingesting data. | Resource | Description | | -------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ | | [Metrics quickstart](/quickstart/metrics) | Walks through the creation of some simple metrics, and is a useful starting point to understand filters available in the Orb webapp. | | [Metric creation guide](/events-and-metrics/construct-metrics) | Learn more about how to compose a metric, its component parts, and how custom metrics can be built via the flexibility of SQL. | ## Item An Item is a representation of what you sell. An Item does not necessarily represent a standalone sellable product; rather, it's likely to be a component of a bundle that your business sells. Items are most directly used when tying data in Orb to a third-party. For example, Item mappings are used to tie to a tax treatment in tax automation software, or to sync line items to a different invoicing solution such as Stripe Invoicing or QuickBooks. Items are most often configured *implicitly* during the creation of other resources. For example, when you create a Metric, you must create or choose an existing Item. To manage your Items and their mappings, navigate to the [Items page](https://app.withorb.com/settings?tab=items\&status=all_items) in the Orb webapp. ## Plan and price A Price is a representation of how much you charge for a given Item, where the usage is determined by a specific Metric. Each price has a specific "price model" (unit pricing, tiered pricing, etc.), which determines how the price is calculated from the aggregate value of the metric. A plan consists of one or more prices, and is often what you might consider your 'list' pricing. For example, if you see multiple tiers of your offering such as Pro, Starter, or Gold, each of these would correspond to a plan in Orb. Although a plan sets up default pricing for a given Item, it's possible to override the pricing for a specific customer when creating a subscription. | Resource | Description | | ---------------------------------------------------- | -------------------------------------------------------------------------------------------- | | [Configure pricing quickstart](/quickstart/pricing) | Shows the creation of a new plan in the Orb webapp, to help familiarize you with the basics. | | [Product catalog](/product-catalog/build-catalog) | Details the different pricing levers that are available in the Orb webapp. | | [Plan API reference](/api-reference/plan/fetch-plan) | Used to fetch the pricing structure and any associated metadata for a specific plan. | ## Subscription A subscription represents a customer's recurring relationship with your business, and ties a Customer to a Plan in Orb. Depending on your business, a subscription may correspond to a specific service agreement or contractual commitment, or it may be the side-effect of your customer signing up to use your product. | Resource | Description | | ------------------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Subscription quickstart](/quickstart/subscriptions) | Guides you through the creation of a new subscription in Orb, and highlights important elements of the subscription lifecycle including your first set of invoices. | | [Subscription creation](/product-catalog/creating-subscriptions) | A detailed overview of the subscription lifecycle, and how core billing mechanics operate on Subscriptions. | | [Subscription modifications](/product-catalog/editing-subscriptions) | A walkthrough of the ways to modify an existing subscription, including creating add-ons, changing pricing, and changing plans. | | [Subscription API reference](/api-reference/subscription/create-subscription) | Used to create subscriptions in Orb, including any overrides required to the pricing configuration of the base plan. | | [Subscription costs API](/api-reference/subscription/fetch-subscription-costs) | Used to fetch accrued costs across a timeframe of the subscription. Integrate this API with your in-product billing portal to provide real-time transparency to your users. | ## Invoice An invoice represents the charges that you're passing on to your customer. Every active subscription in Orb generates invoices throughout its lifecycle, based on the cadence of prices (most commonly monthly). Unlike other billing products which only generate invoices at the *end* of a billing period, active subscriptions in Orb have an upcoming invoice that captures accrued usage charges. This is important because hybrid billing models don't generate *static* invoices; in fact, the current period's draft invoice is often the most important resource. Invoices consist of one or more line items, which are the individual component charges. Most invoices are generated by the subscription, and their line items correspond directly to the component prices on the Plan associated with the Subscription. Similar to other fully-featured invoicing products, invoices can be edited while in draft and may be associated with credit notes to represent invoice adjustments once they are issued. Orb is an invoicing product in addition to a billing product. Orb can deliver invoices to your customers via email, and features end-user facing invoice links to allow paying for an invoice via an integrated payment gateway such as Stripe. This functionality means that you can use Orb as a *replacement* for the Stripe Invoicing product if desired. | Resource | Description | | ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | [Invoicing quickstart](/quickstart/invoicing) | Setting up and testing Orb's invoicing product, including the metadata to customize your invoices for your business. | | [Invoicing guide](/invoicing/introduction) | A collection of guides to help you understand the key pieces of Invoicing functionality that Orb offers. | | [Invoice API reference](/api-reference/invoice/fetch-invoice) | Used to fetch an invoice by ID, or the upcoming invoice. Integrate this API to provide a detailed breakdown of charges to your customer. | ## Object model diagram The following diagram provides a visual reference of the core entities in Orb, and how they relate to each other. Note that this is a simplified diagram, and that some relationships aren't fully detailed here. Object model diagram # Athena Source: https://docs.withorb.com/data-exports/athena ## Prerequisites * [ ] By default, Athena authentication uses role-based access. You will need the trust policy prepopulated with the data syncing service's identifier to grant access. It should look similar to the following JSON object with a proper service account identifier: ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "sts:AssumeRoleWithWebIdentity" ], "Principal": { "Federated": "accounts.google.com" }, "Condition": { "StringEquals": { "accounts.google.com:oaud": "", "accounts.google.com:sub": "" } } } ] } ``` ## Step 1: Create a destination bucket, service policy, and role ### Create Athena target bucket Follow these steps to create a bucket to be used for staging data before transferring to a destination. 1. Navigate to the **S3** service page. 2. Click **Create bucket**. 3. Enter a **Bucket name**, select an **AWS Region**, and modify any of the default settings as desired. Note: **Object Ownership** can be set to "**ACLs disabled**" and **Block Public Access settings for this bucket** can be set to "**Block all public access**" as recommended by AWS. Make note of the Bucket name and AWS Region. 4. Click **Create bucket**. ### Create Athena access policy 1. Navigate to the **IAM** service page, click on the **Policies** navigation tab, and click **Create policy**. 2. Click the JSON tab, and paste the following policy, being sure to replace `ACCOUNT_ID`, `WORKGROUP`, `BUCKET_NAME` and `SCHEMA` with the your account information. * `WORKGROUP` should be `primary` unless otherwise specified during connection configuration. * `BUCKET` should refer to the bucket created in the previous step. * `SCHEMA` used below does not need to be created ahead of time. If it does not exist, it will be created automatically before transferring data. ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Sid": "AllowAthenaAccess", "Effect": "Allow", "Action": [ "athena:GetQueryResults", "athena:StartQueryExecution", "athena:StopQueryExecution", "athena:StartSession", "athena:GetDatabase", "athena:GetDataCatalog", "athena:GetWorkGroup", "athena:GetTableMetadata", "athena:GetQueryExecution" ], "Resource": [ "arn:aws:athena:*:ACCOUNT_ID:workgroup/WORKGROUP" ] }, { "Sid": "AllowGlueAccessToDestinationDatabaseAndTables", "Effect": "Allow", "Action": [ "glue:GetDatabases", "glue:GetDatabase", "glue:GetTables", "glue:GetTable", "glue:GetPartitions", "glue:CreateTable", "glue:CreateDatabase", "glue:UpdateTable", "glue:DeleteTable" ], "Resource": [ "arn:aws:glue:*:ACCOUNT_ID:catalog", "arn:aws:glue:*:ACCOUNT_ID:database/SCHEMA", "arn:aws:glue:*:ACCOUNT_ID:database/default", "arn:aws:glue:*:ACCOUNT_ID:table/SCHEMA/*" ] }, { "Sid": "AllowS3AccessToBucket", "Effect": "Allow", "Action": [ "s3:PutObject", "s3:ListBucket", "s3:GetBucketLocation", "s3:GetObject", "s3:DeleteObject" ], "Resource": [ "arn:aws:s3:::BUCKET_NAME", "arn:aws:s3:::BUCKET_NAME/*" ] } ] } ``` > 🔐 **KMS encryption (optional)** > > If your S3 bucket uses KMS encryption (CMK), add the following statement to the `Statement` array of your IAM policy to allow data encryption/decryption with your KMS key. Encryption with SSE-C is not currently supported. > > ```json theme={null} > { > "Effect": "Allow", > "Action": [ > "kms:GenerateDataKey", > "kms:Decrypt" > ], > "Resource": "arn:aws:kms:REGION_NAME:ACCOUNT_ID:key/KEY_ID" > } > ``` > > Replace `REGION_NAME`, `ACCOUNT_ID`, and `KEY_ID` with your values. > 📘 **Athena vs. S3 permissions** > > Because Athena uses S3 as the underlying storage layer, the Resource access requested in the policy is scoped down via resource-specific permissions in the S3 actions. 3. Click through to the **Review** step, choose a **name** for the policy, for example, `transfer-service-policy` (this will be referenced in the next step), add a description, and click **Create policy**. ### Create role 1. Navigate to the **IAM** service page. 2. Navigate to the **Roles** navigation tab, and click **Create role**. 3. Select **Custom trust policy** and paste the provided trust policy (from the prerequisite) to allow AssumeRole access to this role. Click **Next**. 4. Add the permissions policy created above, and click **Next**. 5. Enter a **Role name**, for example, `transfer-role`, and click **Create role**. 6. Once successfully created, search for the created role in the Roles list, click the role name, and make a note of the **ARN** value. > 🚧 **Alternative authentication method: AWS User with HMAC Access Key ID & Secret Access Key** > > Role based authentication is the preferred authentication mode for Athena based on AWS recommendations. However, HMAC Access Key ID & Secret Access Key is an alternative authentication method that can be used if preferred. > > 1. Navigate to the **IAM** service page. > 2. Navigate to the **Users** navigation tab, and click **Add users**. > 3. Enter a **User name** for the service, for example, `transfer-service`, click **Next**. Under **Select AWS access type**, select the **Access key - Programmatic access** option. Click **Next: Permissions**. > 4. Click the **Attach existing policies directly** option, and search for the name of the policy created in the previous step. Select the policy, and click **Next: Tags**. > 5. Click **Next: Review** and click **Create user**. > 6. In the **Success** screen, record the **Access key ID** and the **Secret access key**. ## Step 2: Add your destination Securely connect your system to Orb using the Data Export UI under Settings -> Data Exports. ## Step 3: Data Management Follow these guidelines to manage your new Athena tables effectively: ### Optimize Iceberg Queries To optimize the performance of your Iceberg tables, use the `OPTIMIZE` command. This command reorganizes the data in a way that improves query efficiency. Execute the following query periodically: ```sql theme={null} OPTIMIZE iceberg_table REWRITE DATA; ``` ### Set Vacuum Properties Iceberg tables can accumulate snapshots over time, which can affect performance. To manage this, set the maximum age for snapshots that the vacuum process should retain: ```sql theme={null} ALTER TABLE iceberg_table SET TBLPROPERTIES ( 'vacuum_max_snapshot_age_seconds'='259200'); ``` The default setting is `432000` seconds, we recommend only updating this if you notice degrading performance. ### Perform Time Travel Queries Iceberg supports accessing historical data snapshots using time travel queries. This feature allows you to query the table as it appeared at a previous point in time, which is useful for audits and rollbacks: ```sql theme={null} SELECT * FROM iceberg_table FOR TIMESTAMP AS OF timestamp; ``` Replace `timestamp` with the specific UNIX timestamp of the snapshot you wish to query. ## Permissions checklist * IAM role trust policy allows the service to assume the role. * IAM policy includes all Athena actions on the target workgroup ARN. * IAM policy includes all Glue actions on the target catalog, database, and tables. * IAM policy includes `s3:PutObject, s3:ListBucket, s3:GetBucketLocation, s3:GetObject, s3:DeleteObject` on the staging bucket and its contents. * If using KMS encryption: `kms:GenerateDataKey` and `kms:Decrypt` granted on the key. ## FAQ ### Q: How is the Athena connection secured? **A:** We use IAM role-based authentication. We assume the IAM role you configure using short-lived credentials. No static access keys are required. All access is scoped to the permissions defined in the role's IAM policy. ### Q: Why are Glue permissions required? **A:** Athena uses the AWS Glue Data Catalog to store and manage table metadata. The Glue permissions allow the service to create and update table definitions as data is synced. ### Q: Do I need to pre-create the Glue database? **A:** No. If the Glue database does not exist, it is created automatically before the first transfer. The `glue:CreateDatabase` permission in the policy enables this. If you prefer to use an existing database, remove `glue:CreateDatabase` from the policy and provide the existing database name. # Aurora mysql Source: https://docs.withorb.com/data-exports/aurora-mysql ## Prerequisites * [ ] If your MySQL database is protected by security groups or other firewall settings, you will need to have the data syncing service's static IP available to complete Step 1. ## Step 1: Allow access Allow write access to a portion of your Aurora MySQL database. ### Configure the Security Group 1. In your **Amazon RDS** > **Databases** list, click the MySQL instance you want to send data to. 2. In the database page, in the **Connectivity & security** tab, make note of the **Endpoint** and the **Port** number. Note that you may need to select the "**Writer instance**" in the DB identifier list to reveal the endpoint. ![](https://storage.googleapis.com/prequel_docs/images/aws-mysql-endpoint-port.png "mysql endpoint port.png") 3. To ensure that the destination is accessible from outside your VPC, click "**Modify**" in the top right, and in the "**Connectivity**" section, within the **Additional configuration** dropdown, confirm the **Publicly accessible** setting is set to **Yes** . Note that it is still only accessible through whitelisted IPs at this point. If connecting via SSH tunnel, this can be set to **No**. ![](https://storage.googleapis.com/prequel_docs/images/aws-mysql-publicly-accessible.png "mysql publicly accessible.png") 4. Returning to the database page, within the "**Writer instance**" details, click one of the VPC security groups (usually `default`). Note: VPC groups are permissive (vs. restrictive) and for instances with multiple VPC security groups, only one needs to be configured with the new inbound rule. ![](https://storage.googleapis.com/prequel_docs/images/aws-mysql-default-security-group.png "vsg.png") 5. In the **Security Groups** section, select the **Inbound rules** tab. 6. Click **Edit inbound rules** and then click **Add rule**. 7. Edit the newly created rule of type **Custom TCP** with the **Port range** noted in the first step (usually `3306`) and a `Custom` **Source** value that includes all of the service IPs. Note: you will need to add `/32` to the end of each IP (CIDR notation). If connecting via SSH tunnel, use the bastion server's security group as the **Source** instead. 8. Click **Save rules**. ![](https://storage.googleapis.com/prequel_docs/images/postgres-add-rule.png "add rule.png") ### Configure network ACLs (access control list) For database instances in a VCP 1. In your RDS dashboard, select the MySQL instance. 2. Click the link to the instance's VPC. 3. Click the **VPC ID**. ![](https://storage.googleapis.com/prequel_docs/images/postgres-vpc-id.png "vpc id.png") 4. In the **Details** section, click on the link under **Main network ACL**. ![](https://storage.googleapis.com/prequel_docs/images/postgres-main-network-acl-id.png "network acl id.png") 5. Click on the network ACL ID. ![](https://storage.googleapis.com/prequel_docs/images/postgres-network-acl-id.png "network acl id.png") #### Edit the inbound rules 6. Click on the **Inbound rules** tab, and check if there is an existing rule with a Source of `0.0.0.0/0` set to `Allow`. (This is a default rule created by AWS. If this rule already exists, skip to **Edit outbound rules**.) ![](https://storage.googleapis.com/prequel_docs/images/postgres-inbound-rules.png "inbound rules.png") 7. Create the inbound rule (if it doesn't exist). Click **Edit inbound rules** and either **Add new rule** or edit an existing rule to allow access to the **port number** of your database instance (usually `5432`) from the Prequel static IP. Click **Save changes**. #### Edit the outbound rules 8. In the ACL menu, select the **Outbound rules** tab, and check if there is an existing rule with a Destination of `0.0.0.0/0` set to `Allow`. (This is a default rule created by AWS. If this rule already exists, skip to the next step.) ![](https://storage.googleapis.com/prequel_docs/images/postgres-outbound-rules.png "outbound rules.png") 9. Create the outbound rule (if it doesn't exist). Click **Edit outbound rules** and edit the rules to allow outbound traffic to ports 1024-65535 for **Destination** `0.0.0.0/0`. ### Optional: SSH tunneling If your database is not publicly accessible, SSH tunneling through a bastion host is supported. 1. Allow inbound SSH (port `22`) from the static egress IP on the bastion host's security group. 2. On the bastion host, create an SSH user and add the service's public key to `~/.ssh/authorized_keys`. Contact support to obtain the public key. 3. Add the bastion host's security group to the database's inbound rules (in place of the static egress IP — see step 7 above). 4. Supply the bastion host address, port, and username when configuring the destination. ## Step 2: Create writer user Create a database user to perform the writing of the source data. 1. Open a connection to your Aurora MySQL database. 2. Create a user for the data transfer by executing the following SQL command. ```sql theme={null} CREATE USER @'%' IDENTIFIED BY ''; ``` 3. Grant user required privileges on the database. ```sql theme={null} GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, CREATE TEMPORARY TABLES, CREATE VIEW ON *.* TO @'%'; ``` > 🚧 **If the `schema/database` already exists:** > > By default, the service creates a new schema (*in MySQL, `schema` is synonymous with `database`*). If you prefer to create the schema yourself before connecting the destination, you must ensure that the writer user has the proper permissions on the schema, using `GRANT ALL PRIVILEGES ON .* TO @'%';` ## Step 3: Add your destination Securely connect your system to Orb using the Data Export UI under Settings -> Data Exports. ## Permissions checklist * RDS security group inbound rules allow the service's egress IP on the configured port (usually `3306`). If using SSH tunneling, allow the egress IP on port 22 on the bastion host's security group instead. * Network ACL inbound rules allow traffic on the configured database port; outbound rules allow ports 1024–65535. * Database user has `SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, CREATE TEMPORARY TABLES, CREATE VIEW` on `*.*`. * If using a pre-created schema: user has `GRANT ALL PRIVILEGES ON .*`. ## FAQ ### Q: How is the Aurora MySQL connection secured? **A:** We connect using the credentials you provide over TCP. Access is restricted by the RDS security group inbound rules, which should allow only the service's static egress IP on the configured port. SSH tunneling through a bastion host is supported for databases not publicly accessible. ### Q: What Aurora MySQL versions are supported? **A:** Aurora MySQL 3.x (MySQL 8.0-compatible) and above are fully supported. Aurora MySQL 2.x (MySQL 5.7-compatible) is best-effort only. ### Q: Do I need to pre-create the schema? **A:** No. The schema is created automatically on first sync. If you pre-create it, grant `ALL PRIVILEGES ON .*` to the writer user. # Aurora postgres Source: https://docs.withorb.com/data-exports/aurora-postgres ## Prerequisites * [ ] If your Postgres database is protected by security groups or other firewall settings, you will need to have the data syncing service's static IP available to complete Step 1. ## Step 1: Allow access Allow write access to a portion of your RDS or Aurora PostgreSQL database. ### Configure the Security Group 1. In your **Amazon RDS** > **Databases** list, click the PostgreSQL instance you want to send data to. 2. In the database page, in the **Connectivity & security** tab, make note of the **Endpoint** and the **Port** number. ![](https://storage.googleapis.com/prequel_docs/images/postgres-endpoint.png "endpoint + port.png") 3. In the **Security** section, ensure that set the **Publicly accessible** setting is set to **Yes** to ensure that the destination is accessible from outside your VPC. Note that it is still only accessible through whitelisted IPs at this point. If connecting via SSH tunnel, this can be set to **No**. ![](https://storage.googleapis.com/prequel_docs/images/postgres-publicly-accessible.png "publicly accessible.png") 4. Click one of the VPC security groups (usually `default`). Note: VPC groups are permissive (vs. restrictive) and for instances with multiple VPC security groups, only one needs to be configured with the new inbound rule. ![](https://storage.googleapis.com/prequel_docs/images/postgres-vpc-security-groups.png "vsg.png") 5. In the **Security Groups** section, select the **Inbound rules** tab. 6. Click **Edit inbound rules** and then click **Add rule**. 7. Edit the newly created rule of type **Custom TCP** with the **Port range** noted in the first step (usually `5432`) and a `Custom` **Source** value that includes all of the service IPs. Note: you will need to add `/32` to the end of each IP (CIDR notation). If connecting via SSH tunnel, use the bastion server's security group as the **Source** instead. 8. Click **Save rules**. ![](https://storage.googleapis.com/prequel_docs/images/postgres-add-rule.png "add rule.png") ### Configure network ACLs (access control list) For database instances in a VCP 1. In your RDS dashboard, select the PostgreSQL instance. 2. Click the link to the instance's VPC. 3. Click the **VPC ID**. ![](https://storage.googleapis.com/prequel_docs/images/postgres-vpc-id.png "vpc id.png") 4. In the **Details** section, click on the link under **Main network ACL**. ![](https://storage.googleapis.com/prequel_docs/images/postgres-main-network-acl-id.png "network acl id.png") 5. Click on the network ACL ID. ![](https://storage.googleapis.com/prequel_docs/images/postgres-network-acl-id.png "network acl id.png") #### Edit the inbound rules 6. Click on the **Inbound rules** tab, and check if there is an existing rule with a Source of `0.0.0.0/0` set to `Allow`. (This is a default rule created by AWS. If this rule already exists, skip to **Edit outbound rules**.) ![](https://storage.googleapis.com/prequel_docs/images/postgres-inbound-rules.png "inbound rules.png") 7. Create the inbound rule (if it doesn't exist). Click **Edit inbound rules** and either **Add new rule** or edit an existing rule to allow access to the **port number** of your database instance (usually `5432`) from the Prequel static IP. Click **Save changes**. #### Edit the outbound rules 8. In the ACL menu, select the **Outbound rules** tab, and check if there is an existing rule with a Destination of `0.0.0.0/0` set to `Allow`. (This is a default rule created by AWS. If this rule already exists, skip to the next step.) ![](https://storage.googleapis.com/prequel_docs/images/postgres-outbound-rules.png "outbound rules.png") 9. Create the outbound rule (if it doesn't exist). Click **Edit outbound rules** and edit the rules to allow outbound traffic to ports 1024-65535 for **Destination** `0.0.0.0/0`. ### Optional: SSH tunneling If your database is not publicly accessible, SSH tunneling through a bastion host is supported. 1. Allow inbound SSH (port `22`) from the static egress IP on the bastion host's security group. 2. On the bastion host, create an SSH user and add the service's public key to `~/.ssh/authorized_keys`. Contact support to obtain the public key. 3. Add the bastion host's security group to the database's inbound rules (in place of the static egress IP — see step 7 above). 4. Supply the bastion host address, port, and username when configuring the destination. ## Step 2: Create writer user Create a database user to perform the writing of the source data. 1. Open a connection to your Amazon RDS PostgreSQL database. 2. Create a user for the data transfer by executing the following SQL command. ```sql theme={null} CREATE USER PASSWORD ''; ``` 3. Grant user `create` and `temporary` privileges on the database. `create` allows the service to create new schemas and `temporary` allows the service to create temporary tables. ```sql theme={null} GRANT CREATE, TEMPORARY ON DATABASE TO ; ``` > 🚧 **If the `schema` already exists:** > > By default, the service creates a new schema based on the destination configuration (in the next step). If you prefer to create the schema yourself before connecting the destination, you must ensure that the writer user has the proper permissions on the schema, using `GRANT ALL ON schema TO ;` ## Step 3: Add your destination Securely connect your system to Orb using the Data Export UI under Settings -> Data Exports. ## Permissions checklist * RDS security group inbound rules allow the service's egress IP on the configured port (usually `5432`). If using SSH tunneling, allow the egress IP on port 22 on the bastion host's security group instead. * Network ACL inbound rules allow traffic on the configured database port; outbound rules allow ports 1024–65535. * Database user has `CREATE` and `TEMPORARY` on the target database. * If using a pre-created schema: user has `GRANT ALL ON SCHEMA `. ## FAQ ### Q: How is the Aurora PostgreSQL connection secured? **A:** We connect using the credentials you provide over TCP. Access is restricted by the RDS security group inbound rules, which should allow only the service's static egress IP on the configured port. SSH tunneling through a bastion host is supported for databases not publicly accessible. ### Q: What Aurora PostgreSQL versions are supported? **A:** Aurora PostgreSQL compatible with PostgreSQL 13 and above are fully supported. PostgreSQL 12-compatible versions are best-effort only. ### Q: Do I need to pre-create the schema? **A:** No. The schema provided in the destination configuration is created automatically on first sync. If you pre-create it, grant `ALL` on the schema to the writer user and you may remove the database-level `CREATE` permission (retain `TEMPORARY`). # Best practices Source: https://docs.withorb.com/data-exports/best-practices When working with the Data Exports product, there are a handful of best practices that we recommend following for the simplest and most straightforward integration of the product and usage of the data. ## Identifiers The IDs on all associated resources point to the same identifiers in Orb’s system. For example, the `invoice_id` field in the Daily Line Item Revenue resource points to the row in the Invoice Metadata resource with that ID. This ID also points to the same invoice in Orb’s system (e.g. through querying in the API). ## Data updates Updates in the data export resources are tracked with the `updated_at` column on each row. This timestamp is used internally by Orb’s system to track when data needs to be exported to data destinations. This timestamp is not an indicator of when the underlying resource was updated in Orb’s system. ## Deletion markers Orb exports deletion markers to indicate that a resource has been deleted. In general, the majority of Orb's resources will have a marker, `deleted_at`, which will be set to the time of deletion, or null if the resource has not been deleted. Certain resources, are append-only and will never be deleted, these do not have a `deleted_at` field present. The following resources do *not* have a deletion marker: * Credit Ledger Entry * Daily Line Item Revenue * Invoice * Invoice Line Item Billing ## Webhooks To help gain better visibility into the operational status of data export process, Orb sends webhooks about transfer successes and failures. These webhooks are documented in [Orb’s webhooks documentation](https://docs.withorb.com/integrations-and-exports/webhooks#webhook-event-types%3A-data-exports) and can be used to instruct downstream processes. For example, you may need to run a downstream job on the raw Orb data to transform and join it against other data sources in your system. This can easily be implemented by listening for the `data_exports.transfer_success` webhook. On the flip side, if you want to be notified when transfers fail, the corresponding `data_exports.transfer_error` webhook can be used. ## Revert and void timestamps Both the Daily Line Item Revenue and Credit Ledger Entry resources contain a `reverted_at` column which indicates when the underlying entry was reverted. This can happen when changes occur in the past (e.g. a subscription cancellation is backdated) that undo operations that have been recorded. In general, we recommend filtering out rows that were reverted when trying to understand the current state. These columns can be used to construct point-in-time queries to understand the state at a given time, by filtering to rows that were active at the time in question. The Invoice Line Item Billing column contains a similar column for voided data called `voided_at`. This column can be used to help understand data that has been voided. This can happen when an invoice was originally sent but was later voided. As with reverted data, we recommend filtering out voided data for most queries, but your exact reporting needs may differ. ## Choosing between base and event resources Several resources are available in two forms: a base resource (e.g., `daily_line_item_revenue`) and an event resource (e.g., `daily_line_item_revenue_event`). Choose based on your accounting requirements: | Scenario | Recommended resource | Reason | | ---------------------------------- | -------------------- | ------------------------------------------------------------ | | **Ad-hoc analysis, dashboards** | Base resource | Simpler queries, `reverted_at` filtering is straightforward | | **Finance/accounting reports** | Event resource | `lock_adjusted_timestamp` respects closed accounting periods | | **Month-end close reconciliation** | Event resource | Ensures consistency with Orb's in-app reports | | **Real-time operational metrics** | Base resource | Lower query complexity, adequate for non-financial use | If your finance team uses [accounting period locks](/revenue-reporting/revenue-recognition#actions-in-the-past-and-accounting-period-locks) in Orb, use the event variants for any reports that feed into your general ledger or financial statements. This ensures your data warehouse matches Orb's revenue reports. ## Lock adjusted timestamps [Accounting period locks in Orb](https://docs.withorb.com/revenue-reporting/revenue-recognition#actions-in-the-past-and-accounting-period-locks) control which monthly time period revenue is recognized. As a result, relevant exported resources that affect revenue will include two timestamps for reporting purposes. | Field Name | Type | Description | | ------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------- | | timestamp | datetime | The true time that the entry was recognized. | | lock\_adjusted\_timestamp | datetime | The time that the entry should be recognized with respect to the accounting period lock posture at the original time. | | is\_revert | boolean | A flag indicating whether this resource represents the creation or reversion of the underlying entry. | The following resources contain these fields: * Credit Note Line Item Event * Committed Ledger Entry Event * Daily Line Item Revenue Event * Invoice Line Item Billing Event When an underlying entry is created, one row with `is_revert = False` will be exported. If that underlying entry is later voided/reverted, another row with `is_revert = True` will be exported. Consequently, each underlying entry for these resources in Orb can map to up to two exported resource entries. To recognize revenue with respect to the accounting period lock posture at the time the resource was created or reverted, use the `lock_adjusted_timestamp` field. Based on the accounting period lock posture at `timestamp`, the original time of recognition, the `lock_adjusted_timestamp` may be shifted to the next open accounting period if the period that `timestamp` falls in was closed. ## Created timestamps The created\_at field captures the exact moment when a resource (`customer`, `subscription`, `invoice`, etc.) was first created in Orb's system, regardless of when it was last modified or when other business events occurred. How is it different from other timestamps? * `created_at`: When the resource was originally created in Orb * `updated_at`: When the resource was last modified * `start_date`/`end_date`: Business-specific dates (e.g., when a subscription starts/ends) The `created_at` timestamp is particularly valuable for: * Historical analysis: Re-create analyses representative of a specific point in time * Backdated actions: Distinguish between when something was created vs. when it was backdated to take effect * Data auditing: Track the chronological order of resource creation * Cohort analysis: Group customers or subscriptions by their actual creation date, not their business effective date For example, if you backdate a subscription to start January 1st but create it on February 15th: * `start_date` = January 1st (business effective date) * `created_at` = February 15th (when it was actually created in Orb) This enables you to analyze "all subscriptions created in February" vs. "all subscriptions starting in January." ## Append-only destinations Orb currently supports S3 as a destination for sending Orb's data. Since S3 is immutable, changes to data are sent as new records instead of updating existing records, because there is no concept of an “update” in S3; only a create is supported. For customers using S3 as a data destination, this means that you will need to deduplicate the data on your end to avoid showing duplicate information. This can be achieved by fetching the latest entry for a given `id` based on the `updated_at` timestamp. Below is a sample snippet of SQL to achieve that: ```sql Deduping data from S3 expandable theme={null} WITH ordered AS ( SELECT * , ROW_NUMBER() OVER (PARTITION BY id ORDER BY updated_at DESC) AS row_number FROM ) SELECT * FROM ordered WHERE row_number = 1; ``` ## Deprecated resources Where possible, we recommend avoiding using any [deprecated resources](/data-exports/resource-types). # Big query Source: https://docs.withorb.com/data-exports/big-query ## Prerequisites * [ ] By default, BigQuery authentication uses role-based access. You will need the data syncing service's service account name available to grant access. It should look like `some-name@some-project.iam.gserviceaccount.com`. ## Understanding role-based authentication in BigQuery > 📘 **Two service accounts involved** > > * **Destination service account (in your GCP project):** You create this service account in Step 1. It has permissions to BigQuery and Cloud Storage and is the identity that performs work inside your project. > * **data syncing service's service account:** Provided to you in the prerequisites. It does not have direct permissions to BigQuery or Cloud Storage. Instead, it is granted permission to "assume" the other service account role (using short-lived tokens via Service Account Token Creator/User), enabling least-privilege, auditable access without sharing keys. **See the end of the configuration guide for Frequently Asked Questions with the BigQuery destination** ## Step 1: Create service account in BigQuery project 1. In the GCP console, navigate to the **IAM & Admin** menu, click into the **Service Accounts** tab, and click **Create service account** at the top of the menu. ![](https://storage.googleapis.com/prequel_docs/images/gcp-create-service-account-menu.png "create service account menu.png") 2. In the first step, name the new **Destination service account** and click **Create and Continue**. ![](https://storage.googleapis.com/prequel_docs/images/gcp-service-account-name-options.png "service account name options.png") 3. In the second step, grant the new **Destination service account** the **BigQuery User** role. This allows creating datasets, submitting load/query jobs, and accessing required metadata during setup. ![](https://storage.googleapis.com/prequel_docs/images/gcp-bigquery-user.png) > ⚖️ **Alternative: Dataset Already Exists - Why:** use least-privilege when your dataset is pre-provisioned. > > * Project: grant `bigquery.jobs.create` to the **Destination service account**. > * Dataset: grant **BigQuery Data Owner** OR a custom role including at minimum: `bigquery.datasets.get`, `bigquery.tables.create`, `bigquery.tables.delete`, `bigquery.tables.get`, `bigquery.tables.getData`, `bigquery.tables.list`, `bigquery.tables.update`, `bigquery.tables.updateData`, `bigquery.routines.get`, `bigquery.routines.list`. 4. Click **Done** to finish creating the account. 5. In the service accounts list, click the newly created **Destination service account** to open its details and make a note of the **email** (this is different from the **data syncing service's service account** from the prerequisites). 6. Navigate to the **Principals with access** tab, click **Grant Access**, and add the following principal and roles: * **Principal:** the provided **data syncing service's service account** (see prerequisites) * **Roles to grant:** **Service Account Token Creator**, **Service Account User** > ⚖️ **Alternative: Key-based Authentication - Why:** only when policy requires, not recommended and presents higher security risk than impersonation. > > * Generate a JSON key for the **Destination service account** and use it to authenticate. > * Steps: IAM & Admin → Service Accounts → open the **Destination service account** → Actions → Manage keys → Add key → Create new key → Key type: JSON → Create. Securely store the key. > > ![](https://storage.googleapis.com/prequel_docs/images/gcp-grant-role.png) ## Step 2: Create a staging bucket 1. Log into the Google Cloud Console and navigate to **Cloud Storage**. Click **Create** to create a new bucket. ![](https://storage.googleapis.com/prequel_docs/images/gcp-create-gcs-bucket.png) 2. Choose a **name** for the bucket. Click **Continue**. Select a **location** for the staging bucket. Make a note of both the **name** and the **location** (region). > 📘 **Choosing a `location` (region)** > > The location you choose for your staging bucket must match the location of your destination dataset in BigQuery. When creating your bucket, be sure to choose a region in which BigQuery is supported [(see BigQuery regions)](https://cloud.google.com/bigquery/docs/locations) > > * If the dataset **does not** exist yet, the dataset will be created for you in the same region where you created your bucket. > * If the dataset **does** exist, the dataset region must match the location you choose for your bucket. 3. Click **Continue** and select the following options according to your preferences. Once the options have been filled out, click **Create**. 4. Ensure the bucket is **not public**. We recommend enabling **Uniform bucket-level access** and keeping all **Public access** blocked. 5. On the **Bucket details** page that appears, click the **Permissions** tab, and then click **Add**. ![](https://storage.googleapis.com/prequel_docs/images/gcp-add-permission-to-bucket.png) 6. In the **New principals** field, add the **Destination service account** created in **Step 1**, select the **Storage Admin** role, and click **Save**. ![](https://storage.googleapis.com/prequel_docs/images/gcp-storage-admin.png) > ⚖️ **Alternative: Understanding GCS Bucket Scope - Why:** reduce privileges while preserving required functionality. > > * We strongly recommend using a new, **dedicated bucket dedicated solely to data transfers for data isolation and to simplify permissions management**. > > However, if policy requires tighter scope than Storage Admin, you can grant only the following minimum actions to the **Destination service account**: `storage.buckets.get`, `storage.objects.list`, `storage.objects.get`, `storage.objects.create`, `storage.objects.delete`. > > * How: use a custom role, or provide both **Storage Legacy Bucket Reader** + **Storage Object User**. > 🧹 **Optional: Add a short retention lifecycle policy** > > You may configure a lifecycle rule on the staging bucket to automatically delete objects older than 2 days as the bucket is not used to persist data. In the bucket **Lifecycle** tab, add a rule with action "Delete object" and condition "Age: 2 days". Note that transfer logic automatically cleans up files after transfer completion, so this is an optional step. ## Step 3: Find Project ID 1. Log into the Google Cloud Console and select the projects list dropdown. 2. Make note of the BigQuery **Project ID**. ![](https://storage.googleapis.com/prequel_docs/images/gcp-project-id.png "project id.png") > 🔒 **Domain-restricted sharing supported** > > This connection supports Google Cloud organization policies that restrict identities by domain. If your organization enforces domain-restricted sharing, you can whitelist our principal according to Google’s guidance on restricting identities by domain. See the Google Cloud documentation: [Restricting identities by domain](https://cloud.google.com/resource-manager/docs/organization-policy/restricting-domains). Contact the team to receive the customer ID to add to your allow list. ## Step 4: Add your destination Securely connect your system to Orb using the Data Export UI under Settings -> Data Exports. ## Permissions checklist * Destination service account exists in your project. * Project: Destination service account has BigQuery User. If dataset is pre-created, instead grant project `bigquery.jobs.create` + dataset-level Data Owner (or custom role with the minimum dataset, table, and routine permissions listed above). * On the Destination service account: grant the data syncing service's service account the Service Account Token Creator and Service Account User roles. * Staging bucket is non-public and in the same region as the BigQuery dataset. * Staging bucket: Destination service account has Storage Admin. If using tighter scope, ensure minimal object and bucket permissions are granted. * Optional: lifecycle rule deletes objects after \~2 days. ## FAQ ### Q: Why is a GCS bucket required? **A:** We use staging-assisted load to use BigQuery's native bulk-upload path, maximizing throughput to your destination. ### Q: How long does data remain in the GCS bucket? **A:** Data is not persisted in the staging bucket and is deleted after each transfer. You may optionally add a lifecycle rule to auto-delete objects after \~2 days. ### Q: Is BigQuery supported across regions? **A:** Yes. BigQuery is supported across all [GCP-supported regions](https://cloud.google.com/storage/docs/locations). Ensure your BigQuery dataset and staging bucket are located in the same region. ### Q: I've updated permissions - why am I still seeing errors? **A:** GCP IAM services can often take up to 10 minutes to propagate. Please wait a few minutes and try again. ### Q: Why are two service accounts involved? Why is service account impersonation required? **A:** You create one service account in your project with BigQuery/Storage permissions, and we use our service account to impersonate yours. This means we never handle your private keys, all operations appear in your audit logs, access is via short-lived tokens, and you can revoke access anytime through your own IAM permissions. Direct service account access is not supported. # Databricks Source: https://docs.withorb.com/data-exports/databricks ## Prerequisites * [ ] By default, this Databricks integration makes use of Unity Catalog data governance features. You will need Unity Catalog enabled on your Databricks Workspace. ## Step 1: Create a SQL warehouse Create a new SQL warehouse for data writing. 1. Log in to the Databricks account. 2. In the navigation pane, click **SQL Warehouses**. 3. In the SQL Warehouses console, click **Create SQL Warehouse**. 4. In the **New SQL Warehouse** menu, choose a **Name** and configure the options for the new SQL warehouse. Under "Advanced options" ensure "Unity Catalog" is in the **On** position and click **Create**. ![](https://storage.googleapis.com/prequel_docs/images/databricks-new-sql-warehouse-ui-refresh.png "databricks-new-sql-warehouse-ui-refresh.png") ## Step 2: Configure Access ### Option A: OAuth Secret (Recommended for most users) 1. In your Databricks workspace, click your username or icon in the top right, click **Settings**, **Identity and access**, and next to the **Service Principals** options, click **Manage**. 2. Click the **Add service principal** button, click **Add new** in the modal, enter a display name and click **Add**. 3. Click on the newly created Service Principal, and under the **Secrets** tab, click **Generate secret**. 4. Enter a lifetime for the secret (e.g., 90 days, 180 days, or 365 days), click **Generate** and make a note of the **Secret** value and **Client ID**. 5. Navigate back to the **SQL Warehouses** section of your Workspace, click the **SQL Warehouses** tab, and select the **SQL Warehouse** you created in **Step 1**. Click **Permissions** in the top right, search for and select the **Service Principal** you created, select the **Can use** permission, and click **Add**. 6. In the Databricks UI, select the **Catalog** tab, and select the target **Catalog**. Within the catalog **Permissions** tab, click **Grant**. In the following modal, select the **Service principal** for which you generated the OAuth token, select `USE CATALOG`, and click **Grant**. 7. Under the target **Catalog**, select the target **schema** (e.g., `main.default`, or create a new target schema). Within the schema **Permissions** tab, click **Grant**. In the following modal, select the **principal** for which you generated the access token, and select either `ALL PRIVILEGES` or the following 9 privileges and then click **Grant**: * `USE SCHEMA` * `APPLY TAG` * `MODIFY` * `READ VOLUME` * `SELECT` * `WRITE VOLUME` * `CREATE MATERIALIZED VIEW` * `CREATE TABLE` * `CREATE VOLUME` ### Option B: Personal Access Token (PAT) Collect connection information and create an access token for the data transfer service. 1. In the **SQL Warehouses** console, select the SQL warehouse you created in **Step 1**. 2. Click the **Connection Details** tab, and make a note of the **Server hostname**, **Port**, and **HTTP path**. ![](https://storage.googleapis.com/prequel_docs/images/databricks-server-path-ui-refresh.png "databricks-server-path-ui-refresh.png") 3. Click the link to Create a **personal access token**. ![](https://storage.googleapis.com/prequel_docs/images/databricks-create-personal-access-token-ui-refresh.png "create_a_personal_access_token.png") 4. Click **Generate New Token**. 5. Name the token with a descriptive comment and assign the token lifetime. A longer lifetime will ensure you do not have to update the token as often. If PAT scopes are enabled on your workspace, select the `sql` scope. Click **Generate**. ![](https://storage.googleapis.com/prequel_docs/images/databricks-pat-scopes.png "databricks-pat-scopes.png") 6. In the pop up that follows, **copy the token** and securely save the token. > 🚧 Using a Service Principal & a Personal Access Token ("PAT") tied to the Service Account > > You may prefer to create a **Service Principal** with a PAT to use for authentication instead of using your account's Personal Access Token. To do so, use the following steps to create a Service Principal and generate a PAT on behalf of the Service Principal. > > 1. In your Databricks workspace, click your username or icon in the top right, click **Settings**, **Identity and access**, and next to the **Service Principals** options, click **Manage**. > 2. Click the **Add service principal** button, click **Add new** in the modal, enter a display name and click **Add**. > 3. Click on the newly created Service Principal, and under **Entitlements** select **Databricks SQL Access** and **Workspace Access**. Click **Update**, and make a note of the **Application ID** of your newly created Service Principal. > 4. Back in the **Admin Settings** menu, click the **Advanced** section (under the **Workspace admin** menu). In the **Access Control** section, next to the **Personal Access Tokens** row, click **Permission Settings**. Search for and select the **Service Principal** you created, select the **Can use** permission, click **Add**, and then **Save**. > 5. Navigate back to the **SQL Warehouses** section of your Workspace, click the **SQL Warehouses** tab, and select the **SQL Warehouse** you created in **Step 1**. Click **Permissions** in the top right, search for and select the **Service Principal** you created, select the **Can use** permission, and click **Add**. > 6. Use your terminal to generate a **Service Principal Access Token** using your Personal Access Token generated above. Record the **token value**. This token can now be used as the access token for the connection. > > ```bash theme={null} > curl --request POST "https://.cloud.databricks.com/api/2.0/token-management/on-behalf-of/tokens" \ > --header "Authorization: Bearer " \ > --data '{ > "application_id": "", > "lifetime_seconds": , > "comment": "" > }' > ``` 7. In the Databricks UI, select the **Catalog** tab, and select the target **Catalog**. Within the catalog **Permissions** tab, click **Grant**. In the following modal, select the **principal** for which you generated the access token, select `USE CATALOG`, and click **Grant**. 8. Under the target **Catalog**, select the target **schema** (e.g., `main.default`, or create a new target schema). Within the schema **Permissions** tab, click **Grant**. In the following modal, select the **principal** for which you generated the access token, and select either `ALL PRIVILEGES` or the following 9 privileges and then click **Grant**: * `USE SCHEMA` * `APPLY TAG` * `MODIFY` * `READ VOLUME` * `SELECT` * `WRITE VOLUME` * `CREATE MATERIALIZED VIEW` * `CREATE TABLE` * `CREATE VOLUME` > 🔐 IP Access Lists (optional) > > If your workspace enforces Databricks IP Access Lists, allowlist the static egress IP(s) used by the data transfer service so connections and test sessions can open successfully. See Databricks documentation: [Databricks IP Access Lists](https://learn.microsoft.com/en-us/azure/databricks/security/network/front-end/ip-access-list). ## Step 3: Add your destination Securely connect your system to Orb using the Data Export UI under Settings -> Data Exports. ## Permissions checklist * Workspace: Service principal or user has permission to use the target SQL warehouse ("Can use"). * Unity Catalog: `USE CATALOG` on the target catalog; appropriate privileges on the target schema (e.g., `USE SCHEMA`, `CREATE TABLE`, and read/write volume privileges as needed), or `ALL PRIVILEGES` on the schema. * If using Hive Metastore (instead of UC): select `hive` metastore explicitly and configure required object storage staging (bucket and credentials), with write/delete permissions. * Network: If IP Access Lists are enabled, the static egress IP(s) are allowlisted. > 🧹 **Optional: Add a short retention lifecycle policy (Hive Metastore only)** > > If using an S3 staging bucket with Hive Metastore, you may configure a lifecycle rule on the bucket to automatically delete objects older than 2 days as the bucket is not used to persist data. In the bucket **Management** tab, click **Create lifecycle rule**, set an expiration action for current versions of objects with a 2-day age. Note that transfer logic automatically cleans up files after transfer completion, so this is an optional step. ## FAQ ### Q: What supported authentication method is recommended to securely connect to Databricks? **A:** Recommended: OAuth with a service principal. OAuth issues short-lived tokens, scopes access via the principal's entitlements and UC grants, and supports centralized rotation and revocation. Personal Access Tokens (PATs) are supported where policy requires, but they are long-lived bearer tokens and typically inherit broader, user-level permissions. ### Q: What permissions are required? **A:** The connection identity needs `Can use` on the SQL warehouse, `USE CATALOG` on the target catalog, and schema-level privileges to create/manage tables (or `ALL PRIVILEGES` on the schema). Missing `USE CATALOG` is a frequent cause of test-connection failures. ### Q: What credentials and connection details are required? **A:** Provide Server hostname, HTTP path, Catalog, Schema, and an OAuth client (service principal) or PAT with warehouse access. Collect host/path from the SQL Warehouses console. ### Q: Do you support Unity Catalog and Hive Metastore? **A:** Yes. Unity Catalog is the default. For Hive Metastore, explicitly select `hive` in configuration and supply an S3 staging bucket and credentials. Using the wrong metastore type can cause discovery or permission issues. # Data Exports Overview Source: https://docs.withorb.com/data-exports/introduction Orb supports managed, daily exports of resource types to your data destination of your choice. Typically, this is a data warehouse such as Redshift or Snowflake, but other types of data stores are also supported. Exporting data from Orb can be done directly within the Orb UI; you can configure data exports directly within the UI. [Start using Orb data](https://app.withorb.com/settings?tab=data_exports) to power your workflows and reports, or talk to us to upgrade for access. ## Export mechanics Orb's data exports are managed by Orb. This managed solution exports data directly to your destination of choice. ### Supported destinations Orb supports the following data destinations: * [Amazon Athena](/data-exports/athena) * [AWS RDS & AWS Aurora](/data-exports/aurora-postgres) * [AWS Aurora MySQL](/data-exports/aurora-mysql) * [BigQuery](/data-exports/big-query) * [Databricks](/data-exports/databricks) * [MotherDuck](/data-exports/motherduck) * [Postgres](/data-exports/postgres) * [Redshift](/data-exports/redshift) * [Amazon S3](/data-exports/s3) * [Snowflake](/data-exports/snowflake) We're actively working to support more destinations. If you don't see your destination of choice listed, please reach out to your Orb representative. ### How Orb syncs changes For nearly all resource types, Orb provides an *incremental* sync, which significantly reduces the data that is synced on a regular basis. The first sync that is run on the data exporter is a full sync (and may transfer a large number of resources). Future syncs after this are incremental and will only capture the updates that occur. In Orb's system, changes to the core resource types (e.g. creations, updates, or deletes) are tracked internally. These records are used to help understand what changed and when. Using these records, Orb's system is able to power the logic that determines which resource types need to be exported in a given timeframe during an incremental sync. Orb's internal process has two, dependent stages: 1. Serializing / dumping resources in preparation for the transfer process 2. The transfer process where these resources are queried then copied to the end data store ## Export UI Note that only account admins are able to access the data exports UI and interact with data export connections. The UI for the Data Export product can be found under "Data exports" within the Settings page. data-exports-empty-state A new data export connection can be added by clicking the "Create connection" button and filling out information about your data destination. By default, accounts can create one data export connection in live mode and cannot create data export connections in test mode. Your account may have a different connection limit configured by Orb; if you need to add more connections, contact your Orb representative. The information needed about your data destination will differ depending on which destination system you're choosing. The UI within Orb will dynamically provide you with the relevant inputs needed to configure a data export. data-exports-destination-info After filling in the required information about your data destination, you will be able to choose which resource types you want to sync. data-exports-connect-resource-types After creating a new data export connection, you'll be able to view it directly in the UI. data-exports-enable-disable The UI also provides the ability to enable, disable, or delete your data export destination depending on its current state. Use the three vertical dots to see these options. The current status of the destination is also listed within the table and will appear differently if it is disabled. # Motherduck Source: https://docs.withorb.com/data-exports/motherduck ## Step 1: Create a Database Create a new database for data writing. Skip this step if you already have a database prepared. 1. Log in to the MotherDuck account. 2. Click the plus icon next to "Attached Databases". 3. Enter the desired name of your database. ## Step 2: Create an Access Token Collect connection information and create an access token for the data transfer service. 1. In the navigation dropdown, select **Settings**. ![](https://storage.googleapis.com/prequel_docs/images/motherduck-endpoints-navigation.png "navigation.png") 2. Click the **Create token** button to create a \*\* access token\*\*. ![](https://storage.googleapis.com/prequel_docs/images/motherduck-endpoints-settings.png "settings.png") 3. Name the token with a descriptive comment and assign the token lifetime. A longer lifetime will ensure you do not have to update the token as often. Ensure that the **Token Type** is set to "Read/Write Token". Click **Generate**. ![](https://storage.googleapis.com/prequel_docs/images/motherduck-endpoints-create-token.png "create-token.png") 4. In the pop up that follows, **copy the token** and securely save the token. ## Step 3: Add your destination Securely connect your system to Orb using the Data Export UI under Settings -> Data Exports. ## Permissions checklist * Access token generated with **Read/Write Token** type. * Token lifetime set according to your rotation policy. * Target database exists in MotherDuck. ## FAQ ### Q: How is the MotherDuck connection secured? **A:** We authenticate using a MotherDuck access token. Generate a dedicated Read/Write token in MotherDuck Settings and provide it during destination setup. The token grants scoped access without sharing your account credentials. ### Q: How do I update my access token? **A:** Generate a new token in MotherDuck Settings, update the destination configuration with the new token, and revoke the old one. ### Q: Do I need to pre-create the schema? **A:** No. The schema provided in the destination configuration is created automatically on first sync. # Postgres Source: https://docs.withorb.com/data-exports/postgres ## Prerequisites * [ ] If your Postgres database is protected by security groups or other firewall settings, you will need to have the data syncing service's static IP available to complete Step 1. ## Step 1: Allow access Create a rule in a security group or firewall settings to whitelist: * incoming connections to your host and port (usually `5432`) from `35.192.85.117`. * outgoing connections from ports `1024` to `65535` to `35.192.85.117`. > 📘 Optional: SSH tunneling > > If your database is not accessible from the public internet, SSH tunneling through a bastion host is supported. Allow inbound SSH (port `22`) from the static egress IP on the bastion host, create an SSH user with the service's public key in `~/.ssh/authorized_keys` (contact support for the key), and grant the bastion host's IP access to the database port in place of the static egress IP. Provide the bastion host address, port, and username in the destination configuration. ## Step 2: Create writer user Create a database user to perform the writing of the source data. 1. Open a connection to your PostgreSQL database. 2. Create a user for the data transfer by executing the following SQL command. ```sql theme={null} CREATE USER PASSWORD ''; ``` > 🚧 Credential character limitations > > For user credentials containing special characters, please avoid using the following characters: `@`, `[`, `]`, `/`, `?`, `#`, `"`, `\\`, `+`, space, `&`, `:`, `%` as these characters can break connection string parsing. 3. Grant user `create` and `temporary` privileges on the database. `create` allows the service to create new schemas and `temporary` allows the service to create temporary tables. ```sql theme={null} GRANT CREATE, TEMPORARY ON DATABASE TO ; ``` > 🚧 **If the `schema` already exists** > > By default, the service creates a new schema based on the destination configuration (in the next step). If you prefer to create the schema yourself before connecting the destination, you must ensure that the writer user has the proper permissions on the schema, using `GRANT ALL ON schema TO ;` ## Step 3: Add your destination Securely connect your system to Orb using the Data Export UI under Settings -> Data Exports. ## Permissions checklist * Database user has `CREATE` and `TEMPORARY` on the target database. * If using a pre-created schema: user has `GRANT ALL ON SCHEMA `. * Firewall or security group allows the service's egress IP on port 5432. If using SSH tunneling, allow the egress IP on port 22 on the bastion host instead. ## FAQ ### Q: How is the PostgreSQL connection secured? **A:** We connect using the credentials you provide (host, port, username, password) over TCP. If your database is not publicly accessible, SSH tunneling through a bastion host is supported. The service uses public key authentication for bastion access. ### Q: What PostgreSQL versions are supported? **A:** PostgreSQL 13 and above are fully supported. PostgreSQL 12 is best-effort only. ### Q: Do I need to pre-create the schema? **A:** No. The schema provided in the destination configuration is created automatically on first sync. If you pre-create it, grant `ALL` on the schema to the writer user and you may remove the database-level `CREATE` permission (retain `TEMPORARY`). # Redshift Source: https://docs.withorb.com/data-exports/redshift ## Prerequisites * [ ] If your Redshift security posture requires IP whitelisting, have the data syncing service's static IP available during the following steps. It will be required in Step 2. * [ ] By default, Redshift authentication uses role-based access. You will need the trust policy prepopulated with the data-syncing service's identifier to grant access. It should look similar to the following JSON object with a proper service account identifier: ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "sts:AssumeRoleWithWebIdentity" ], "Principal": { "Federated": "accounts.google.com" }, "Condition": { "StringEquals": { "accounts.google.com:oaud": "", "accounts.google.com:sub": "" } } } ] } ``` ## Step 1: Create a Limited User in Redshift 1. Connect to Redshift using the SQL client. 2. Execute the following query to create a user to write the data (replace `` with a password of your choice). ```sql theme={null} CREATE USER PASSWORD ''; ``` > 📘 **Creating a user without a password.** > > Role based auth does not require a password. You may create the user using `CREATE USER PASSWORD DISABLE;`. 3. Grant user `create` and `temporary` privileges on the database. `create` allows the service to create new schemas and `temporary` allows the service to create temporary tables. ```sql theme={null} GRANT CREATE, TEMPORARY ON DATABASE TO ; ``` > 📘 **The schema will be created during the first sync** > > The schema name supplied as part of Step 4 will be created during the first connection. It does not need to be created manually in the destination ahead of time. > 🚧 **If the `schema` already exists** > > By default, the service creates a new schema based on the destination configuration. If you prefer to create the schema yourself before connecting the destination, you must ensure that the writer user has the proper permissions on the schema, using `GRANT ALL ON schema TO ;` > > Once you've provided the `GRANT ALL` permission on the schema, you can safely remove the `CREATE` permission on the database (but you must retain the `TEMPORARY` permission on the database). ## Step 2: Whitelist connection 1. In the Redshift console, click **Clusters**, and make a note of the **cluster** name. 2. Select the cluster you would like to connect. 3. In the **General information** pane, make note of the **Endpoint** details. You may need to use the **copy** icon to copy the full details to discover the full endpoint and port number. ![](https://storage.googleapis.com/prequel_docs/images/redshift-endpoint-details.png "redshift endpoint details.png") 4. Click the **Properties** tab. 5. Scroll down to the **Network and security settings** section. 6. In the VPC security group field, select a security group to open it. ![](https://storage.googleapis.com/prequel_docs/images/redshift-vpc-security-groups.png "redshift vpc s groups.png") 7. In the Security Groups window, click **Inbound rules**. 8. Click **Edit inbound rules**. 9. In the Edit the Inbound rules window, follow the steps below to create custom TCP rules for `35.192.85.117`: a. Select **Custom TCP** in the drop-down menu. b. Enter your Redshift port number. (likely `5439`) c. Enter **35.192.85.117**. d. Click **Add rule**. > 📘 Public accessibility and subnet requirements > > For IP allowlisting from outside your VPC, the Redshift cluster must be set to **Publicly accessible** and deployed in a **public subnet** with a route to an Internet Gateway. For private Redshift clusters, SSH tunneling is supported. Contact the team for instruction on configuring an SSH tunnel for your Redshift cluster. ## Step 3: Create a staging bucket ### Create staging bucket 1. Navigate to the S3 service page. 2. Click Create bucket. 3. Enter a **Bucket name** and modify any of the default settings as desired. Note: **Object Ownership** can be set to "**ACLs disabled**" and **Block Public Access settings for this bucket** can be set to "**Block all public access**" as recommended by AWS. Make note of the Bucket name and AWS Region. 4. Click **Create bucket**. > 🧹 **Optional: Add a short retention lifecycle policy** > > You may configure a lifecycle rule on the staging bucket to automatically delete objects older than 2 days as the bucket is not used to persist data. In the bucket **Management** tab, click **Create lifecycle rule**, set an expiration action for current versions of objects with a 2-day age. Note that transfer logic automatically cleans up files after transfer completion, so this is an optional step. ### Create policy 1. Navigate to the **IAM** service page, click on the **Policies** navigation tab, and click **Create policy**. 2. Click the JSON tab, and paste the following policy, being sure to replace `BUCKET_NAME` with the name of the bucket chosen above, and REGION\_NAME, ACCOUNT\_ID, CLUSTER\_NAME, USERNAME, and DATABASE\_NAME with the proper Redshift values. 1. **Note**: the first bucket permission in the list applies to `BUCKET_NAME` whereas the second permission applies only to the bucket's contents — `BUCKET_NAME/*` — an important distinction. ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::BUCKET_NAME" }, { "Effect": "Allow", "Action": [ "s3:PutObject", "s3:GetObject", "s3:DeleteObject" ], "Resource": "arn:aws:s3:::BUCKET_NAME/*" }, { "Effect": "Allow", "Action": "redshift:GetClusterCredentials", "Resource": [ "arn:aws:redshift:REGION_NAME:ACCOUNT_ID:dbuser:CLUSTER_NAME/USERNAME", "arn:aws:redshift:REGION_NAME:ACCOUNT_ID:dbname:CLUSTER_NAME/DATABASE_NAME" ] } ] } ``` > 🔐 **KMS encryption (optional)** > > If your S3 staging bucket uses KMS encryption (CMK), add the following statement to the `Statement` array of your IAM policy to allow data encryption/decryption with your KMS key. Encryption with SSE-C is not currently supported. > > ```json theme={null} > { > "Effect": "Allow", > "Action": [ > "kms:GenerateDataKey", > "kms:Decrypt" > ], > "Resource": "arn:aws:kms:REGION_NAME:ACCOUNT_ID:key/KEY_ID" > } > ``` > > Replace `REGION_NAME`, `ACCOUNT_ID`, and `KEY_ID` with your values. > 🚧 Credential character limitations > > For user credentials containing special characters, please avoid using the following characters: `@`, `[`, `]`, `/`, `?`, `#`, `"`, `\\`, `+`, space, `&`, `:`, `%` as these characters can break connection string parsing. 3. Click through to the **Review** step, choose a **name** for the policy, for example, `transfer-service-policy` (this will be referenced in the next step), add a description, and click **Create policy**. ### Create role 1. Navigate to the **IAM** service page. 2. Navigate to the **Roles** navigation tab, and click **Create role**. 3. Select **Custom trust policy** and paste the provided trust policy (from the prerequisite) to allow AssumeRole access to this role. Click **Next**. 4. Add the permissions policy created above, and click **Next**. 5. Enter a **Role name**, for example, `transfer-role`, and click **Create role**. 6. Once successfully created, search for the created role in the Roles list, click the role name, and make a note of the **ARN** value. > 🚧 **Alternative authentication method: AWS User with HMAC Access Key ID & Secret Access Key** > > Role based authentication is the preferred authentication mode for Redshift based on AWS recommendations. However, HMAC Access Key ID & Secret Access Key is an alternative authentication method that can be used if preferred. > > 1. Navigate to the **IAM** service page. > 2. Navigate to the **Users** navigation tab, and click **Add users**. > 3. Enter a **User name** for the service, for example, `transfer-service`, click **Next**. Under **Select AWS access type**, select the **Access key - Programmatic access** option. Click **Next: Permissions**. > 4. Click the **Attach existing policies directly** option, and search for the name of the policy created in the previous step. Select the policy, and click **Next: Tags**. > 5. Click **Next: Review** and click **Create user**. > 6. In the **Success** screen, record the **Access key ID** and the **Secret access key**. ## Step 4: Add your destination Securely connect your system to Orb using the Data Export UI under Settings -> Data Exports. ## Permissions checklist * Redshift database user exists and has `CREATE` and `TEMPORARY` on the database. If you pre-created the schema, ensure `GRANT ALL ON SCHEMA TO `. * IAM role trust policy allows data syncing service's to assume the role. * IAM policy includes: * `redshift:GetClusterCredentials` on your target cluster (db user and db name resources). * S3 `ListBucket` on `arn:aws:s3:::BUCKET_NAME`. * S3 `GetObject`, `PutObject`, `DeleteObject` on `arn:aws:s3:::BUCKET_NAME/*`. * Network allowlisting (if enforced) permits egress IP/CIDR for the Redshift port (typically 5439). ## FAQ ### Q: How is the Redshift connection secured? **A:** We use role-based authentication with your AWS IAM Role. The data syncing service's assumes your role to obtain short-lived database credentials and network access can be constrained by allowlisting the static egress IPs noted above. ### Q: Why is an S3 bucket required? **A:** Redshift's high-throughput path loads data from S3 using `COPY`. We stage files briefly in your bucket to maximize throughput and reliability. Files are cleaned up after load. ### Q: What are the `oaud` vs `sub` IDs used for? **A:** These are identity claims used in the IAM trust policy when federating from GCP to AWS. `sub` uniquely identifies our Google principal in federation. `oaud` is an additional claim used to bind role assumption to your organization. ### Q: Why am I getting authentication errors with Redshift? **A:** Common causes: * Missing or incorrect permission on `redshift:GetClusterCredentials` (ensure it targets the correct cluster ARN and region/account). * Trust policy mismatch (the data syncing service's principal isn't permitted to assume your role). * Using a Serverless workgroup permission or `redshift-serverless:GetCredentials` instead of provisioned cluster + `redshift:GetClusterCredentials`. * Propagation delay: IAM changes can take a few minutes to apply. Retry after 5-10 minutes. ### Q: Do I need to pre-create the schema? **A:** No. The schema provided in the destination configuration is created automatically on first sync. If you pre-create it, grant `ALL` on the schema to the writer user and you may remove the database-level `CREATE` permission (retain `TEMPORARY`). # Exported resource types Source: https://docs.withorb.com/data-exports/resource-types ## Resources The table below provides an overview of the supported resource types, their schema, and any notes on their sync behavior: | Resource Type | Sync Cadence | Notes | | ----------------------------------------------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Accounting Period Lock](#accounting-period-lock) | Daily | [Accounting Period Lock](/revenue-reporting/revenue-recognition#actions-in-the-past-and-accounting-period-locks) | | [Adjustment](#adjustment) | Daily | [Adjustments](/product-catalog/build-catalog#adjustments) | | [Adjustment Interval](#adjustment-interval) | Daily | [Adjustments](/product-catalog/build-catalog#adjustments) | | [Billable Metric](#billable-metric) | Daily | [Metric](/core-concepts#metric) | | [Commitment](#commitment) | Daily | Represents a customer's spend commitment on a subscription. See [commitment management](/enterprise/enterprise-billing#enterprise-prepaid-credits-and-commitment-management). | | [Commitment Ledger Entry Event](#commitment-ledger-entry-event) | Daily | Tracks drawdown activity against a [Commitment](#commitment) as separate created and reverted events. | | [Coupon](#coupon) | Daily | [Coupons](/product-catalog/adjustments#coupons) | | [Coupon Redemption](#coupon-redemption) | Daily | [How to use Coupons](/product-catalog/adjustments#coupons) | | [Credit Ledger Entry](#credit-ledger-entry) | Daily | Orb syncs all committed (i.e. non-pending) ledger entries. | | [Credit Ledger Entry Event](#credit-ledger-entry-event) | Daily | This resource represents the same data as [Credit Ledger Entry](#credit-ledger-entry), but includes additional fields to enable recognition with respect to accounting period locks. | | [Credit Note](#credit-note) | Daily | [Credit notes](/invoicing/credit-notes) | | [Credit Note Line Item](#credit-note-line-item) | Daily | | | [Credit Note Line Item Event](#credit-note-line-item-event) | Daily | This resource represents the same data as [Credit Note Line Item](#credit-note-line-item), but includes additional fields to enable recognition with respect to accounting period locks. | | [Customer](/api-reference/customer/fetch-customer) | Daily | [Customer](/core-concepts#customer) | | [Customer Balance Transaction](#customer-balance-transaction) | Daily | | | [Daily Credit Note Line Item Event](#daily-credit-note-line-item-event) | Daily | This resource represents the same data as [Credit Note Line Item](#credit-note-line-item), but includes additional fields to enable recognition with respect to accounting period locks. | | [Daily Line Item Revenue](#daily-line-item-revenue) | Daily | | | [Daily Line Item Revenue Event](#daily-line-item-revenue-event) | Daily | This resource represents the same data as [Daily Line Item Revenue](#daily-line-item-revenue), but includes additional fields to enable recognition with respect to accounting period locks. | | [Dimensional Price Group](#dimensional-price-group) | Daily | [Dimensional pricing](/product-catalog/price-configuration#dimensional-pricing) | | [Invoice Line Item Billing](#invoice-line-item-billing) | Daily | | | [Invoice Line Item Billing Event](#invoice-line-item-billing-event) | Daily | This resource represents the same data as [Invoice Line Item Billing](#invoice-line-item-billing), but includes additional fields to enable recognition with respect to accounting period locks. | | [Invoice Metadata](#invoice-metadata) | Daily | | | [Item](#item) | Daily | [Item](/core-concepts#item) | | [License](#license) | Daily | [License allocations](/product-catalog/license-allocations) | | [License Type](#license-type) | Daily | [License allocations](/product-catalog/license-allocations#license-type) | | [Payment Application](#payment-application) | Daily | [Payments](/invoicing/payments) | | [Payment Attempt](#payment-attempt) | Daily | [Payments](/invoicing/payments) | | [Plan](/api-reference/plan/fetch-plan) | Daily | [Plan and Price](/core-concepts#plan-and-price) | | [Price](#price) | Daily | [Plan and Price](/core-concepts#plan-and-price) | | [Price Interval](#price-interval) | Daily | [Plan and Price](/core-concepts#plan-and-price) | | [Subscription](/api-reference/subscription/fetch-subscription) | Daily | [Subscription](/core-concepts#subscription). The response mirrors the shape of the fetch subscription [endpoint](/api-reference/subscription/fetch-subscription). This resource excludes adjustment intervals; see the [Adjustment Interval](#adjustment-interval) and the [Adjustment](#adjustment) export resources. | | [Subscription version](#subscription-version) | Daily | This captures the [subscription schedule](/core-concepts#subscription) of a subscription, specifically allowing you to understand plan transitions. | Dataexportstable Pn ## General guidance ### API-based resources A subset of Orb's exported resources are based off of the API response shape. These are not directly defined below, but linked to the relevant API reference documentation, and may change as the API evolves. In addition, these resources often include JSON fields that require further parsing and are less well defined. #### List of resources The following resource schemas are dependent on the API resources: * [Customer](/api-reference/customer/fetch-customer) * [Invoice](/api-reference/invoice/fetch-invoice) * [Plan](/api-reference/plan/fetch-plan) * [Subscription](/api-reference/subscription/fetch-subscription) Customer, Plan, and Subscription resources will always have deletion markers in addition to the API response shape. #### Nested resources API-based resources often contain nested fields that reference *other* objects. For example, the Plan resource references Prices which are a separate, top-level resource. Additionally, API‑based resources often include nested or “helper” fields that duplicate parts of other objects for convenience. These embedded fields are intended to make simple questions easier to answer in a single query and to keep exports readable without many joins. "Helper fields" are **snapshots** taken when the top‑level resource was last updated, and may not reflect subsequent changes to the nested objects. For an up‑to‑date or authoritative view, you should always join against the dedicated export for that object. Dedicated resource recommendations to optimize for freshness of common helper fields: | Snapshot helper fields | Recommended join for dedicated alternative | | -------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Subscription
`current_billing_period_start_date`
`current_billing_period_end_date` | Invoice Metadata
Join on `subscription_id`, where `invoice_type` = subscription.
Treat `invoice_date` plus plan/price cadence as the billed period. | | Subscription
`current_billing_period_start_date`
`current_billing_period_end_date` | Daily Line item Revenue
Group by `subscription_id` and `invoice_id.`
Use`timeframe_start, timeframe_end` as period boundaries. | | Plan
`prices` | Price
Join on `price_id` | ## Resource schemas ### Accounting Period Lock #### Overview The [accounting period lock](/revenue-reporting/revenue-recognition#actions-in-the-past-and-accounting-period-locks) resource represents the history of the lock postures of monthly accounting periods in Orb. By default, accounting period entries are initialized in Orb as open, and can be closed by a user. Orb only generates entries for accounting periods when closing open periods or opening closed periods. * If no entries exist for any accounting periods, this means that all periods are currently open. * If only one closed accounting period exists, this means that all periods before that closed period are also closed, and all periods after it are open. If a closed accounting period is reopened, a new open entry for that period will be generated with the `sequence_number` incremented. Importantly, a single entry for an accounting period only describes the lock posture of that period at a certain time. To determine the timeline of actions performed on an accounting period, reference all entries for that period in order of ascending `sequence_number`. To determine the current state of an accounting period, reference the entry for that period with the largest `sequence_number`. #### Schema | Field Name | Type | Description | | ------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | The ID of the accounting period lock. | | updated\_at | timestamp | The last updated date of the accounting period lock. | | created\_at | nullable timestamp | The timestamp when the accounting period lock was created. | | period\_start | timestamp | The start timestamp (inclusive) of the period in the account's timezone. This timestamp is used to determine when revenue events should be recognized. | | period\_end | timestamp | The end timestamp (exclusive) of the period in the account's timezone. This timestamp is used to determine when revenue events should be recognized. | | conceptual\_period\_start\_date | date | The start timestamp (inclusive) of the period, at midnight UTC time. This timestamp is used to refer to accounting periods consistently. | | conceptual\_period\_end\_date | date | The end timestamp (exclusive) of the period, at midnight UTC time. This timestamp is used to refer to accounting periods consistently. | | locked\_at | nullable timestamp | The timestamp that the period was closed. | | unlocked\_at | timestamp | The timestamp that the period was opened. | | locked\_by\_user\_email | nullable string | The email of the user who closed the period.

Note that if the user was deleted before the resource was exported, this email may also be null. | | unlocked\_by\_user\_email | nullable string | The email of the user who opened the period. If null, this means that this period was opened by Orb as part of initializing a period.

Note that if the user was deleted before the resource was exported, this email may also be null. | | sequence\_number | int | A key used to order accounting period lock entries in chronological order. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Adjustment #### Overview The adjustment resource represents a minimum, maximum, or discount that can be applied to a single price or a set of prices. See the [Adjustments](/product-catalog/build-catalog#adjustments) section for more information. The adjustment table is most useful when joining with other tables using the `adjustment_id` field. For example, * you can see how a coupon is defined by the `adjustment_id` linked on the coupon resource * you can better understand how an adjustment is applied onto a daily line item **revenue** resource by inspecting the `adjustment_ids` column in the [Daily Line Item Revenue](#daily-line-item-revenue) resource #### Schema | Field Name | Type | Description | | ----------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | The ID of the adjustment. | | updated\_at | timestamp | The last updated date of the adjustment. | | created\_at | nullable timestamp | The timestamp when the adjustment was created. | | applies\_to\_price\_ids | string\[] | The IDs of the prices that the adjustment applies to. | | filters | nullable JSON | The set of filters used to scope which prices the adjustment applies to, beyond `applies_to_price_ids`. | | adjustment\_type | string | The type of adjustment. Can be "minimum", "maximum", "percentage\_discount", "amount\_discount", or "usage\_discount". | | adjustment\_value | decimal | The value of the adjustment. For percentage discounts, this is a percentage. For amount discounts, maximums, and minimums, this is an amount in the price's currency. For usage discounts, this is a percentage. | | currency | string | The currency of the adjustment. This is determined by the price's currency. | | reason | nullable string | The reason for the adjustment. | | item\_id | nullable string | The ID of the item, as it appears on an invoice. This is only applicable for minimums. | | is\_invoice\_level | boolean | Whether the adjustment is an invoice level adjustment or price-level adjustment. | | is\_trial | boolean | Whether the adjustment represents a trial discount. | | plan\_id | nullable string | The ID of the plan, if the adjustment is associated with a plan. | | plan\_phase\_order | nullable int | The order of the plan phase, if the adjustment is associated with a plan and phase. | | min\_plan\_version | nullable int | The minimum plan version, if the adjustment is associated with a plan and version. | | max\_plan\_version | nullable int | The maximum plan version, if the adjustment is associated with a plan and version. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Adjustment Interval #### Overview The adjustment interval resource represents a time frame during which an adjustment applies to a subscription. [See editing subscriptions](/product-catalog/editing-subscriptions) and [add or edit price intervals](/api-reference/price-interval/add-or-edit-price-intervals) for more information. #### Schema | Field Name | Type | Description | | --------------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------ | | id | string | The ID of the adjustment interval. | | updated\_at | timestamp | The last updated date of the adjustment interval. | | created\_at | nullable timestamp | The timestamp when the adjustment interval was created. | | subscription\_id | string | The ID of the subscription. | | customer\_id | string | The ID of the customer. | | adjustment\_id | string | The ID of the adjustment. | | applies\_to\_price\_interval\_ids | JSON | The IDs of the price intervals that the adjustment applies to. | | start\_time\_inclusive | timestamp | The start date of the adjustment interval. | | end\_time\_exclusive | nullable timestamp | The end date of the adjustment interval, if defined. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Billable Metric #### Overview A billable metric defines an aggregation over usage events. #### Schema | Field Name | Type | Description | | ----------- | ------------------ | ------------------------------------------------------------------------------------------------------ | | id | string | The ID of the billable metric. | | updated\_at | timestamp | The last updated date of the billable metric. | | created\_at | nullable timestamp | The timestamp when the billable metric was created. | | metadata | json | User specified key-value pairs for the resource. If not present, this defaults to an empty dictionary. | | name | string | The user provided name of this billable metric. | | description | string | The user provided description of this billable metric. | | item\_id | string | The id of the item, see the [Item](#item) export. | | status | string | Available options: active, draft, or archived. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Commitment #### Overview A commitment represents a customer's spend commitment on a subscription, drawn down over the commitment's term. See [commitment management](/enterprise/enterprise-billing#enterprise-prepaid-credits-and-commitment-management) for more information. Drawdown activity against a commitment is captured by the [Commitment Ledger Entry Event](#commitment-ledger-entry-event) resource. #### Schema | Field Name | Type | Description | | ---------------------------------- | ------------------ | -------------------------------------------------------------------------------------------------------------------------- | | id | string | The ID of the commitment. | | updated\_at | timestamp | The last updated date of the commitment. | | created\_at | nullable timestamp | The timestamp when the commitment was created. | | customer\_id | string | The ID of the customer. | | subscription\_id | string | The ID of the subscription the commitment applies to. | | currency | string | The currency of the commitment. | | target\_amount | decimal | The target amount of the commitment, in the commitment's currency. | | commit\_type | string | The type of commitment. | | status | string | The status of the commitment. | | drawdown\_basis | string | How usage is drawn down against the commitment (for example, `post_prepaid`). | | eligible\_price\_filters | nullable JSON | The set of filters used to scope which prices draw down against the commitment. If null, no additional scoping is applied. | | start\_time\_inclusive | timestamp | The start date (inclusive) of the commitment. | | end\_time\_exclusive | timestamp | The end date (exclusive) of the commitment. | | true\_up\_invoicing\_cadence\_unit | nullable string | The cadence unit at which any true-up amount is invoiced, if a true-up is configured. | | commitment\_price\_id | nullable string | The ID of the price Orb uses to bill this commitment. For commitments with a true-up, this is the true-up price. | | true\_up\_price\_id | nullable string | The ID of the price used to invoice any true-up amount, if a true-up is configured. Equivalent to `commitment_price_id`. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Commitment Ledger Entry Event #### Overview The Commitment Ledger Entry Event resource represents the operations that draw down a [Commitment](#commitment) (for example, increments and decrements), captured as separate created and reverted events. This resource is helpful for understanding how a commitment gets burned down over time. Like the other `*Event` resources, entries are emitted as distinct creation and revert events (distinguished by the `is_revert` field). Unlike the [Credit Ledger Entry Event](#credit-ledger-entry-event) resource, this resource does not expose a `lock_adjusted_timestamp`. #### Schema | Field Name | Type | Description | | ------------------------ | ------------------ | ------------------------------------------------------------------------------------------------------ | | id | string | A composite ID of the form `{source_id}-{is_revert}`. | | updated\_at | timestamp | The last updated date of the commitment ledger entry. | | created\_at | nullable timestamp | The timestamp when this event was created or reverted in Orb. | | source\_id | string | The ID of the commitment ledger entry. | | is\_revert | boolean | Whether this entry represents a creation or revert event. | | timestamp | timestamp | The original time that this entry was created or reverted in Orb. | | commitment\_id | string | The ID of the commitment. | | customer\_id | string | The customer ID associated with this entry. | | currency | string | The currency of the commitment ledger entry. | | amount | decimal | The amount that was applied on this commitment ledger entry. | | starting\_balance | decimal | The beginning balance of the ledger. | | ending\_balance | decimal | The ending balance of the ledger. | | entry\_type | string | The type of the ledger entry. | | ledger\_sequence\_number | integer | The sequence number of the ledger entry. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Coupon #### Overview A coupon represents a reusable discount configuration that can be applied either as a fixed or percentage amount to an invoice or subscription. #### Schema | Field Name | Type | Description | | -------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | The ID of the coupon. | | updated\_at | timestamp | The last updated date of the coupon. | | created\_at | nullable timestamp | The timestamp when the coupon was created. | | redemption\_code | string | This string can be used to redeem this coupon for a given subscription. | | discount | json | A JSON blob representing either a PercentageDiscount or AmountDiscount | | times\_redeemed | int | The number of times this coupon has been redeemed. | | duration\_in\_months | nullable int | This allows for a coupon's discount to apply for a limited time (determined in months); a null value here means "unlimited time". | | max\_redemptions | nullable int | The maximum number of redemptions allowed for this coupon before it is exhausted; null here means "unlimited". | | archived\_at | nullable timestamp | An archived coupon can no longer be redeemed. Active coupons will have a value of null for archived\_at; this field will be non-null for archived coupons. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Coupon Redemption #### Overview The Coupon Redemption resource type represents a coupon that has been redeemed by a customer. #### Schema | Field Name | Type | Description | | ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------ | | id | string | The ID of the coupon redemption. | | updated\_at | timestamp | The last updated date of the coupon redemption. | | created\_at | nullable timestamp | The timestamp when the coupon redemption was created. | | start\_date | timestamp | The start date of the coupon redemption. | | end\_date | nullable timestamp | An optional end date of the coupon redemption. | | coupon\_id | string | The ID of the coupon. | | adjustment\_id | string | The ID of the adjustment. | | subscription\_id | string | The ID of the subscription. | | customer\_id | string | The ID of the customer. | | unredeemed\_time | nullable timestamp | An optional timestamp of when the coupon was unredeemed, if it was unredeemed. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Credit Ledger Entry #### Overview The Credit ledger entry resource represents the various operations that occur for each credit block (e.g. increments and decrements). This resource is helpful to understand how a credit block gets burned down. #### Schema | Field Name | Type | Description | | ------------------------ | ------------------ | ---------------------------------------------------------------- | | id | string | The ID of the credit ledger entry. | | updated\_at | timestamp | The last updated date of the credit ledger entry. | | created\_at | timestamp | The timestamp when the credit ledger entry was created. | | reverted\_at | nullable timestamp | If set, the time at which this entry was reverted. | | amount | string | The amount that was applied on this credit ledger entry. | | credit\_block | JSONB | The associated credit block for this entry | | currency | string | The currency of the credit ledger entry. | | customer | JSONB | The customer record associated with this entry. | | description | nullable string | An optional description of the entry. | | starting\_balance | string | The beginning balance of the ledger. | | ending\_balance | string | The ending balance of the ledger. | | entry\_status | string | The status of the ledger entry. | | entry\_type | string | The type of the ledger entry. | | invoice\_id | nullable string | If set, the relevant invoice this entry applies to. | | invoice\_line\_item\_id | nullable string | If set, the relevant invoice line item ID this entry applies to. | | ledger\_sequence\_number | integer | The sequence number of the ledger entry. | | metadata | nullable JSON | If set, the metadata attached to this ledger entry. | | new\_block\_expiry\_date | nullable timestamp | If set, the time at which this expiry has been updated to. | | price\_id | nullable string | If set, the relevant price identifier. | | event\_id | nullable string | If set, the ID of the event associated with this ledger entry. | | void\_amount | nullable string | If set, the amount of the increment that was voided. | | void\_reason | nullable string | If set, the reason for the increment being voided. | ### Credit Ledger Entry Event #### Overview This resource represents the same data as [Credit Ledger Entry](#credit-ledger-entry), but also includes `timestamp`, `lock_adjusted_timestamp`, and `is_revert` fields, enabling the recognition of credit block operations with respect to the [accounting period lock posture at the time of the operations](/data-exports/best-practices#lock-adjusted-timestamps). #### Schema | Field Name | Type | Description | | --------------------------- | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | A composite ID of the form `{source_id}-{is_revert}`. | | updated\_at | timestamp | The last updated date of the credit ledger entry. | | created\_at | nullable timestamp | The timestamp when the credit ledger entry was created. | | source\_id | string | The ID of the credit ledger entry. | | is\_revert | boolean | Whether this entry represents a creation or revert event. | | timestamp | timestamp | The original time that this entry was created or reverted in Orb. | | lock\_adjusted\_timestamp | timestamp | The time that this entry should be recognized based on the accounting period lock posture. | | amount | decimal | The amount that was applied on this credit ledger entry. | | currency | string | The currency of the credit ledger entry. | | invoicing\_currency | nullable string | The currency used for invoicing if it differs from the price's currency.

Today, this only occurs if you are using virtual pricing units. | | customer\_id | string | The customer ID associated with this entry. | | description | nullable string | An optional description of the entry. | | starting\_balance | decimal | The beginning balance of the ledger. | | ending\_balance | decimal | The ending balance of the ledger. | | entry\_status | string | The status of the ledger entry. | | entry\_type | string | The type of the ledger entry. | | invoice\_id | nullable string | If set, the relevant invoice this entry applies to. | | invoice\_line\_item\_id | nullable string | If set, the relevant invoice line item ID this entry applies to. | | ledger\_sequence\_number | integer | The sequence number of the ledger entry. | | block\_id | nullable string | If set, the associated credit block ID for this entry. | | cost\_basis | nullable decimal | If set, the associated credit block cost basis for this entry. | | block\_expiry\_date | nullable timestamp | If set, the associated credit block's expiry date for this entry. | | metadata | nullable JSON | If set, the metadata attached to this ledger entry. | | new\_block\_expiry\_date | nullable timestamp | If set, the time at which this expiry has been updated to. | | price\_id | nullable string | If set, the relevant price identifier. | | recognized\_revenue\_amount | nullable decimal | If set, the converted amount recognized. | | void\_amount | nullable string | If set, the amount of the increment that was voided. | | void\_reason | nullable string | If set, the reason for the increment being voided. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Credit note #### Overview The Credit note resource type represents a credit note that has been issued to a customer. A credit note is a document that can be used to decrease the amount due or issue a credit for an already issued invoice. #### Schema | Field Name | Type | Description | | -------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------- | | id | string | The ID of the credit note. | | updated\_at | timestamp | The last updated date of the credit note. | | created\_at | timestamp | The timestamp when the credit note was created. | | voided\_at | nullable timestamp | The time at which the credit note was voided in Orb, if applicable. | | credit\_note\_number | string | The unique identifier for credit notes. | | invoice\_id | string | The id of the invoice resource that this credit note is applied to. | | memo | nullable string | An optional memo supplied on the credit note. | | reason | nullable string | An optional reason for the credit note. Available options: "Duplicate", "Fraudulent", "Order change", "Product unsatisfactory". | | type | string | The type of credit note. Available options: `refund`, `adjustment`. | | currency | string | The currency of the credit note. | | subtotal | string | The total prior to any creditable invoice-level discounts or minimums. | | total | string | The total including creditable invoice-level discounts or minimums, and tax. | | customer\_id | string | The id of the customer resource that this credit note is applied to. | | credit\_note\_pdf | nullable string | A URL to a PDF of the credit note. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Credit Note Line Item #### Overview The Credit Note Line Item resource type represents a line item that is associated with a credit note. #### Schema | Field Name | Type | Description | | ---------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | id | string | The ID of the credit note line item. | | updated\_at | timestamp | The last updated date of the credit note line item. | | created\_at | timestamp | The timestamp when the credit note line item was created. | | voided\_at | nullable timestamp | The date and time when the credit note was voided. | | credit\_note\_id | string | The ID of the credit note. | | name | string | The name of the corresponding invoice line item. | | amount | string | The amount of the line item, excluding any line item minimums and discounts. | | quantity | nullable number | An optional quantity credited. | | tax\_amounts | dict\[] | Any tax amounts applied onto the line item. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | | timeframe\_start | nullable timestamp | The inclusive timestamp for the start of the relevant period.

Note that entries are scoped to day boundaries, but may start or end in the middle of the day if a plan change occurs. | | timeframe\_end | nullable timestamp | The exclusive timestamp for the end of the relevant period.

Note that entries are scoped to day boundaries, but may start or end in the middle of the day if a plan change occurs. | | line\_item\_id | nullable string | The ID of the line item | | item\_id | nullable string | The ID of the product or service item | | block\_id | nullable string | If set, the associated credit block ID for this line item. | | total | nullable decimal | The total amount of the line item, excluding tax | | total\_with\_tax | nullable decimal | The total amount of the line item, including tax | ### Credit Note Line Item Event #### Overview This resource represents the same data as [Credit Note Line Item](#credit-note-line-item), but also includes `timestamp`, `lock_adjusted_timestamp`, and `is_revert` fields, enabling the recognition of credit note line items with respect to the [accounting period lock posture at the time of credit note issuance](/data-exports/best-practices#lock-adjusted-timestamps). #### Schema | Field Name | Type | Description | | ------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | id | string | A composite ID of the form `{source_id}-{is_revert}`. | | updated\_at | timestamp | The last updated date of the credit note line item. | | created\_at | nullable timestamp | The timestamp when the credit note line item was created. | | source\_id | string | The ID of the credit note line item. | | is\_revert | boolean | Whether this entry represents a creation or revert event. | | timestamp | timestamp | The original time that this entry was created or reverted in Orb. | | lock\_adjusted\_timestamp | timestamp | The time that this entry should be recognized based on the accounting period lock posture. | | credit\_note\_id | string | The ID of the credit note. | | invoice\_id | string | The ID of the associated invoice. | | customer\_id | nullable string | The ID of the customer. | | name | string | The name of the corresponding invoice line item. | | quantity | nullable number | The quantity credited. | | tax\_amounts | dict\[] | Any tax amounts applied onto the line item. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | | timeframe\_start | nullable timestamp | The inclusive timestamp for the start of the relevant period.

Note that entries are scoped to day boundaries, but may start or end in the middle of the day if a plan change occurs. | | timeframe\_end | nullable timestamp | The exclusive timestamp for the end of the relevant period.

Note that entries are scoped to day boundaries, but may start or end in the middle of the day if a plan change occurs. | | invoice\_line\_item\_id | nullable string | The ID of the invoice line item. | | invoicing\_currency | nullable string | The currency used for invoicing if it differs from the price's currency.

Today, this only occurs if you are using virtual pricing units. | | item\_id | nullable string | The ID of the product or service item. | | block\_id | nullable string | If set, the associated credit block ID for this line item. | | total | nullable decimal | The total amount of the line item, excluding tax. | | total\_with\_tax | nullable decimal | The total amount of the line item, including tax. | ### Customer Balance Transaction #### Overview The customer balance is an amount in the customer's currency, which Orb automatically applies to subsequent invoices. This balance can be adjusted manually via Orb's webapp on the customer details page. #### Schema | Field Name | Type | Description | | ----------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | The ID of the customer balance transaction. | | updated\_at | timestamp | The last updated date of the customer balance transaction. | | created\_at | timestamp | The timestamp when the customer balance transaction was created. | | starting\_balance | decimal | The original value of the customer's balance prior to the transaction, in the customer's currency. | | ending\_balance | decimal | The new value of the customer's balance after the transaction, in the customer's currency. | | amount | decimal | The value of the amount changed in the transaction. | | action | string | Available options: `applied_to_invoice`, `manual_adjustment`, `prorated_refund`, `revert_prorated_refund`, `return_from_voiding`, `credit_note_applied`, `credit_note_voided`, `overpayment_refund` | | type | string | Available options: `increment`, `decrement` | | description | nullable string | An optional description provided for manual customer balance adjustments. | | invoice\_id | nullable string | An optional ID if there is an associated invoice. | | credit\_note\_id | nullable string | An optional ID if there is an associated credit note. | | customer\_id | string | The ID of the customer. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Daily Credit Note Line Item Event #### Overview This resource represents the same data as [Credit Note Line Item](#credit-note-line-item), but also includes `timestamp`, `lock_adjusted_timestamp`, and `is_revert` fields, enabling the recognition of credit note line items with respect to the [accounting period lock posture at the time of credit note issuance](/data-exports/best-practices#lock-adjusted-timestamps). Entries are scoped to day boundaries. #### Schema | Field Name | Type | Description | | ------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | id | string | A composite ID of the form `{source_id}-{is_revert}`. | | updated\_at | timestamp | The last updated date of the credit note line item. | | created\_at | nullable timestamp | The timestamp when the credit note line item was created. | | source\_id | string | The ID of the credit note line item. | | is\_revert | boolean | Whether this entry represents a creation or revert event. | | timestamp | timestamp | The original time that this entry was created or reverted in Orb. | | lock\_adjusted\_timestamp | timestamp | The time that this entry should be recognized based on the accounting period lock posture. | | credit\_note\_id | string | The ID of the credit note. | | invoice\_id | string | The ID of the associated invoice. | | invoice\_line\_item\_id | nullable string | The ID of the invoice line item. | | customer\_id | nullable string | The ID of the customer. | | invoicing\_currency | nullable string | The currency used for invoicing. Since credit notes are always issued after invoice issuance, there is only ever one currency. | | name | string | The name of the corresponding invoice line item. | | item\_id | nullable string | The ID of the product or service item. | | block\_id | nullable string | If set, the associated credit block ID for this line item. | | timeframe\_start | nullable timestamp | The inclusive timestamp for the start of the relevant period.

Note that entries are scoped to day boundaries, but may start or end in the middle of the day if a plan change occurs. | | timeframe\_end | nullable timestamp | The exclusive timestamp for the end of the relevant period.

Note that entries are scoped to day boundaries, but may start or end in the middle of the day if a plan change occurs. | | total | nullable decimal | The total amount of the line item, excluding tax. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Daily Line Item Revenue #### Overview The Daily Line Item Revenue resource type can be used to track the daily quantity and associated costs for invoice line items. This resource type will function similarly to the [subscription costs endpoint](/api-reference/subscription/fetch-subscription-costs), but with more fine grained access to information. This information can be used to power use cases like: * Daily revenue recognition workflows * Running pricing exercises As mentioned, this resource will track daily quantities, but will reflect intra-day plan changes where applicable. This means that rows may span time periods less than a day in cases where a plan changes. Values in the export are periodic values (as opposed to cumulative values) and only relevant to the applicable timeframe. The Daily Line Item Revenue resource type is conceptually an append only resource type. Entries in this table can be reverted. For example, when a backdated operation occurs, this may result in old values changing. When entries are reverted, they will have a corresponding timestamp in their `reverted_at` field but will still exist in the table. This means that historical queries can be constructed to understand what values were returned in the past. The Daily Line Item Revenue resource type relies on upstream processes running to calculate these values. Importantly, this processing relies on the grace period ending before it can run. This means that entries in this resource type will be delayed. On average, we expect this delay to be approximately three days. #### Schema | Field Name | Type | Description | | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | id | string | The ID of the daily line item revenue object. | | updated\_at | timestamp | The last updated date of the daily line item revenue object. | | created\_at | nullable timestamp | The timestamp when the daily line item revenue object was created. | | customer\_id | string | The ID of the customer. | | subscription\_id | nullable string | The ID of the subscription.

Note that a subscription ID may not always be present due to things like one-off invoices. | | pricing\_currency | string | The currency of the underlying price. | | invoicing\_currency | nullable string | The currency used for invoicing if it differs from the price's currency.

Today, this only occurs if you are using virtual pricing units. | | item\_id | string | The ID of the item | | invoice\_id | string | The ID of the invoice | | invoice\_line\_item\_id | string | The ID of the invoice line item | | price\_id | string | The ID of the price | | billable\_metric\_id | nullable string | The ID of the billable metric.

Note that a billable metric ID may not be present if the associated price is not usage based. | | plan\_id | nullable string | The ID of the plan.

Note that it may not always be present due to things like one-off invoices. | | timeframe\_start | timestamp | The inclusive timestamp for the start of the relevant period.

Note that entries are scoped to day boundaries, but may start or end in the middle of the day if a plan change occurs. | | timeframe\_end | timestamp | The exclusive timestamp for the end of the relevant period.

Note that entries are scoped to day boundaries, but may start or end in the middle of the day if a plan change occurs. | | line\_item\_start\_date | timestamp | The start date of the associated line item. | | line\_item\_end\_date | timestamp | The end date of the associated line item. | | invoice\_date | nullable timestamp | The invoice date. | | quantity | decimal | The price's quantity for the relevant timeframe. | | subtotal | decimal | The list price before any adjustments have been applied. | | adjusted\_subtotal | decimal | The list price after adjustments have been applied.

Called the "total" in the subscription costs API. This has been renamed to avoid confusion. | | amount | decimal | The list price after: adjustments have been applied, prepaid credits have been applied, license allocations and license overage write-offs have been applied, overage conversions have been applied, and partial invoice amounts have been applied. | | rounded\_amount | decimal | The amount after rounding occurs. If no rounding occurs, this value will be the same as `amount`. This value does not include tax. | | credits\_applied | decimal | The number of credits used. | | license\_allocation\_applied | nullable decimal | The amount of license allocation applied to this line item. Only populated for line items whose usage drew down from a license allocation. | | license\_allocation\_overage | nullable decimal | The license allocation overage written off for this line item. Only non-zero when the license allocation is configured to write off overage. | | conversion\_rate | decimal | The conversion rate between the price currency and the invoicing currency.

If they are the same, this is 1. | | adjustments | JSONB

An array of dictionaries with: adjustment ID, adjustment type, amount, and price IDs. | The adjustments made to line items to account for maximums, minimums, and discounts. | | sub\_line\_items | nullable JSONB

If this field is null, the number of sub-line items exceeds 500. This is differentiated from cases with no sub-line items, where the column will be empty JSON. | The serialized sub-line items from the invoice.

This field can contain up to 500 sub-line items for a given line item. If the number of sub-line items exceeds 500, this field will not contain any sub-line items. | | is\_partial\_invoice | boolean | True if this line item was part of a [Threshold invoice](https://docs.withorb.com/reference/create-subscription#threshold-billing) | | partially\_invoiced\_amount | nullable decimal | If this is from a threshold invoice, the amount that was partially invoiced. | | reverted\_at | nullable timestamp | When this entry was reverted.

Entries are reverted when backdated operations occur that change the amounts for a given invoice line item on a specified day. Entries are never deleted and are only marked as reverted to allow for historical tracking of values returned on a given day in the past.

A row is valid in this table if reverted\_at is null. | ### Daily Line Item Revenue Event #### Overview This resource represents the same data as [Daily Line Item Revenue](#daily-line-item-revenue), but also includes `timestamp`, `lock_adjusted_timestamp`, and `is_revert` fields, enabling revenue recognition with respect to the [accounting period lock posture at the time of original recognition](/data-exports/best-practices#lock-adjusted-timestamps). #### Schema | Field Name | Type | Description | | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | A composite ID of the form `{source_id}-{is_revert}`. | | updated\_at | timestamp | The last updated date of the daily line item revenue object. | | created\_at | nullable timestamp | The timestamp when the daily line item revenue object was created. | | source\_id | string | The ID of the daily line item revenue object. | | is\_revert | boolean | Whether this entry represents a creation or revert event. | | timestamp | timestamp | The original time that this entry was created or reverted in Orb. | | lock\_adjusted\_timestamp | timestamp | The time that this entry should be recognized based on the accounting period lock posture. | | customer\_id | string | The ID of the customer. | | subscription\_id | nullable string | The ID of the subscription.

Note that a subscription ID may not always be present due to things like one-off invoices. | | pricing\_currency | string | The currency of the underlying price. | | invoicing\_currency | nullable string | The currency used for invoicing if it differs from the price's currency.

Today, this only occurs if you are using virtual pricing units. | | item\_id | string | The ID of the item | | invoice\_id | string | The ID of the invoice | | invoice\_line\_item\_id | string | The ID of the invoice line item | | price\_id | string | The ID of the price | | billable\_metric\_id | nullable string | The ID of the billable metric.

Note that a billable metric ID may not be present if the associated price is not usage based. | | plan\_id | nullable string | The ID of the plan.

Note that it may not always be present due to things like one-off invoices. | | timeframe\_start | timestamp | The inclusive timestamp for the start of the relevant period.

Note that entries are scoped to day boundaries, but may start or end in the middle of the day if a plan change occurs. | | timeframe\_end | timestamp | The exclusive timestamp for the end of the relevant period.

Note that entries are scoped to day boundaries, but may start or end in the middle of the day if a plan change occurs. | | line\_item\_start\_date | nullable timestamp | The start date of the associated line item. | | line\_item\_end\_date | nullable timestamp | The end date of the associated line item. | | invoice\_date | nullable timestamp | The invoice date. | | quantity | decimal | The price's quantity for the relevant timeframe. | | subtotal | decimal | The list price before any adjustments have been applied. | | adjusted\_subtotal | decimal | The list price after adjustments have been applied.

Called the "total" in the subscription costs API. This has been renamed to avoid confusion. | | amount | decimal | The list price after: adjustments have been applied, prepaid credits have been applied, license allocations and license overage write-offs have been applied, overage conversions have been applied, and partial invoice amounts have been applied. | | rounded\_amount | decimal | The amount after rounding occurs. If no rounding occurs, this value will be the same as `amount`. This value does not include tax. | | correction\_amount | nullable decimal | The amount of corrections applied to the line item, reflected in `rounded_amount`. | | credits\_applied | decimal | The number of credits used. | | license\_allocation\_applied | nullable decimal | The amount of license allocation applied to this line item. Only populated for line items whose usage drew down from a license allocation. | | license\_allocation\_overage | nullable decimal | The license allocation overage written off for this line item. Only non-zero when the license allocation is configured to write off overage. | | conversion\_rate | decimal | The conversion rate between the price currency and the invoicing currency.

If they are the same, this is 1. | | adjustments | JSONB

An array of dictionaries with: adjustment ID, adjustment type, amount, and price IDs. | The adjustments made to line items to account for maximums, minimums, and discounts. | | sub\_line\_items | nullable JSONB

If this field is null, the number of sub-line items exceeds 500. This is differentiated from cases with no sub-line items, where the column will be empty JSON. | The serialized sub-line items from the invoice.

This field can contain up to 500 sub-line items for a given line item. If the number of sub-line items exceeds 500, this field will not contain any sub-line items. | | is\_partial\_invoice | boolean | True if this line item was part of a [Threshold invoice](https://docs.withorb.com/reference/create-subscription#threshold-billing) | | partially\_invoiced\_amount | nullable decimal | If this is from a threshold invoice, the amount that was partially invoiced. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Dimensional Price Group #### Overview The dimensional price group resource represents a dimensional price group that can be reused across prices and plans. See [Create dimensional price group](/api-reference/dimensional-price-group/create-dimensional-price-group) for more information. The dimensional price group table is most useful when joining with the Price table using the `dimensional_price_group_id` field. For example, you can use `dimensional_price_group_id` in the Price schema above to understand which prices partition the same billable metric (and by which `dimensions`), and use the `dimension_values` in the Price schema to understand which partition a particular price represents. #### Schema | Field Name | Type | Description | | --------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------ | | id | string | The ID of the dimensional price group. | | updated\_at | timestamp | The last updated date of the dimensional price group. | | created\_at | timestamp | The timestamp when the dimensional price group was created. | | name | string | The name of the price group. | | external\_dimensional\_price\_group\_id | nullable string | The ID of the price group in the external system. | | billable\_metric\_id | string | The ID of the billable metric that the price group partitions | | dimensions | JSON (array of strings) | The dimensions that the price group partitions | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Invoice Line Item Billing #### Overview The Invoice Line Item Billing resource type can be used to track the individual line item values for invoices that have been issued / billed. This information can be used to power use cases like: * Calculating the precise amounts discounted per line item * Breaking down the amounts billed for a specific price or billable metric The Invoice Line Item Billing resource type is conceptually an append only resource type. There are infrequent cases where entries can be voided (usually from a requested manual intervention). If this has occurred, it will be reflected in the `voided_at` column. The Invoice Line Item Revenue resource type relies on an invoice being issued before rows are written. Importantly, invoice issuing relies on the grace period ending before it can occur. This means that entries in this resource type will be delayed. #### Schema | Field Name | Type | Description | | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | The ID of the invoice line item billing object. | | updated\_at | timestamp | The last updated date of the invoice line item billing object. | | created\_at | nullable timestamp | The timestamp when the invoice line item billing object was created. | | customer\_id | string | The ID of the customer. | | subscription\_id | nullable string | The ID of the subscription.

Note that a subscription ID may not always be present due to things like one-off invoices. | | pricing\_currency | string | The currency of the underlying price. | | invoicing\_currency | nullable string | The currency used for invoicing if it differs from the price's currency.

Today, this only occurs if you are using virtual pricing units. | | item\_id | string | The ID of the item | | invoice\_id | string | The ID of the invoice | | invoice\_line\_item\_id | string | The ID of the invoice line item | | price\_id | string | The ID of the price | | billable\_metric\_id | nullable string | The ID of the billable metric.

Note that a billable metric ID may not be present if the associated price is not usage based. | | plan\_id | nullable string | The ID of the plan.

Note that it may not always be present due to things like one-off invoices. | | block\_id | nullable string | If set, the associated credit block ID for this line item. | | invoice\_date | nullable timestamp | The invoice date. | | timeframe\_start | timestamp | The inclusive timestamp for the start of the relevant period.

Note that entries are scoped to day boundaries, but may start or end in the middle of the day if a plan change occurs. | | timeframe\_end | timestamp | The exclusive timestamp for the end of the relevant period.

Note that entries are scoped to day boundaries, but may start or end in the middle of the day if a plan change occurs. | | quantity | decimal | The price's quantity for the relevant timeframe. | | subtotal | decimal | The list price before any adjustments have been applied. | | adjusted\_subtotal | decimal | The list price after adjustments have been applied.

Called the "total" in the subscription costs API. This has been renamed to avoid confusion. | | amount | decimal | The list price after: adjustments have been applied, prepaid credits have been applied, license allocations and license overage write-offs have been applied, overage conversions have been applied, and partial invoice amounts have been applied. | | rounded\_amount | decimal | The amount after rounding occurs. If no rounding occurs, this value will be the same as `amount`. This value does not include tax. | | tax\_amount | decimal | The tax amount calculated for this line item. | | credits\_applied | decimal | The number of credits used. | | license\_allocation\_applied | nullable decimal | The amount of license allocation applied to this line item. Only populated for line items whose usage drew down from a license allocation. | | license\_allocation\_overage | nullable decimal | The license allocation overage written off for this line item. Only non-zero when the license allocation is configured to write off overage. | | conversion\_rate | decimal | The conversion rate between the price currency and the invoicing currency.

If they are the same, this is 1. | | adjustments | JSONB

An array of dictionaries with: adjustment ID, adjustment type, amount, and price IDs. | The adjustments made to line items to account for maximums, minimums, and discounts. | | sub\_line\_items | nullable JSONB

If this field is null, the number of sub-line items exceeds 500. This is differentiated from cases with no sub-line items, where the column will be empty JSON. | The serialized sub-line items from the invoice.

This field can contain up to 500 sub-line items for a given line item. If the number of sub-line items exceeds 500, this field will not contain any sub-line items. | | is\_partial\_invoice | boolean | True if this line item was part of a [Threshold invoice](https://docs.withorb.com/reference/create-subscription#threshold-billing) | | partially\_invoiced\_amount | nullable decimal | If this is from a threshold invoice, the amount that was partially invoiced. | | voided\_at | nullable timestamp | When this entry was voided.

Entries are very infrequently voided and usually correspond to manual actions that have taken place at the request of the customer. | ### Invoice Line Item Billing Event #### Overview This resource represents the same data as [Invoice Line Item Billing](#invoice-line-item-billing), but also includes `timestamp`, `lock_adjusted_timestamp`, and `is_revert` fields, enabling revenue recognition with respect to the [accounting period lock posture at the time of original issuance](/data-exports/best-practices#lock-adjusted-timestamps). #### Schema | Field Name | Type | Description | | ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | A composite ID of the form `{source_id}-{is_revert}`. | | updated\_at | timestamp | The last updated date of the invoice line item billing object. | | created\_at | nullable timestamp | The timestamp when the invoice line item billing object was created. | | source\_id | string | The ID of the invoice line item billing entry. | | is\_revert | boolean | Whether this entry represents a creation or revert event. | | timestamp | timestamp | The original time that this entry was created or reverted in Orb. | | lock\_adjusted\_timestamp | timestamp | The time that this entry should be recognized based on the accounting period lock posture. | | customer\_id | string | The ID of the customer. | | subscription\_id | nullable string | The ID of the subscription.

Note that a subscription ID may not always be present due to things like one-off invoices. | | pricing\_currency | string | The currency of the underlying price. | | invoicing\_currency | nullable string | The currency used for invoicing if it differs from the price's currency.

Today, this only occurs if you are using virtual pricing units. | | item\_id | string | The ID of the item | | invoice\_id | string | The ID of the invoice | | invoice\_line\_item\_id | string | The ID of the invoice line item | | price\_id | string | The ID of the price | | billable\_metric\_id | nullable string | The ID of the billable metric.

Note that a billable metric ID may not be present if the associated price is not usage based. | | plan\_id | nullable string | The ID of the plan.

Note that it may not always be present due to things like one-off invoices. | | block\_id | nullable string | If set, the associated credit block ID for this line item. | | timeframe\_start | timestamp | The inclusive timestamp for the start of the relevant period.

Note that entries are scoped to day boundaries, but may start or end in the middle of the day if a plan change occurs. | | timeframe\_end | timestamp | The exclusive timestamp for the end of the relevant period.

Note that entries are scoped to day boundaries, but may start or end in the middle of the day if a plan change occurs. | | invoice\_date | nullable timestamp | The invoice date. | | quantity | decimal | The price's quantity for the relevant timeframe. | | subtotal | decimal | The list price before any adjustments have been applied. | | adjusted\_subtotal | decimal | The list price after adjustments have been applied.

Called the "total" in the subscription costs API. This has been renamed to avoid confusion. | | amount | decimal | The list price after: adjustments have been applied, prepaid credits have been applied, license allocations and license overage write-offs have been applied, overage conversions have been applied, and partial invoice amounts have been applied. | | rounded\_amount | decimal | The amount after rounding occurs. If no rounding occurs, this value will be the same as `amount`. This value does not include tax. | | correction\_amount | nullable decimal | The amount of corrections applied to the line item, reflected in `rounded_amount`. | | tax\_amount | decimal | The tax amount calculated for this line item. | | credits\_applied | decimal | The number of credits used. | | license\_allocation\_applied | nullable decimal | The amount of license allocation applied to this line item. Only populated for line items whose usage drew down from a license allocation. | | license\_allocation\_overage | nullable decimal | The license allocation overage written off for this line item. Only non-zero when the license allocation is configured to write off overage. | | conversion\_rate | decimal | The conversion rate between the price currency and the invoicing currency.

If they are the same, this is 1. | | adjustments | JSONB

An array of dictionaries with: adjustment ID, adjustment type, amount, and price IDs. | The adjustments made to line items to account for maximums, minimums, and discounts. | | sub\_line\_items | nullable JSONB

If this field is null, the number of sub-line items exceeds 500. This is differentiated from cases with no sub-line items, where the column will be empty JSON. | The serialized sub-line items from the invoice.

This field can contain up to 500 sub-line items for a given line item. If the number of sub-line items exceeds 500, this field will not contain any sub-line items. | | is\_partial\_invoice | boolean | True if this line item was part of a [Threshold invoice](https://docs.withorb.com/reference/create-subscription#threshold-billing) | | partially\_invoiced\_amount | nullable decimal | If this is from a threshold invoice, the amount that was partially invoiced. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Invoice Metadata #### Overview The Invoice Metadata resource represents a subset of data from the invoice resource. This resource also exposes draft invoices within Orb, which can be deleted at any time. #### Schema | Field Name | Type | Description | | | ---------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | - | | id | string | The ID of the invoice. | | | updated\_at | timestamp | The last updated date of the invoice. | | | created\_at | timestamp | The timestamp when the invoice was created. | | | currency | string | The currency of the invoice. | | | customer\_id | string | The ID of the customer. | | | due\_date | nullable timestamp | The time the invoice must be paid by. | | | invoice\_date | timestamp | An optional invoice date set on creation. | | | invoice\_number | nullable string | The identifying invoice number. | | | invoice\_type | string | The type of invoice, either "one\_off" or "subscription". | | | issued\_at | nullable timestamp | The time the invoice was sent out. | | | memo | nullable string | The optional memo for the reason for the invoice. | | | paid\_at | nullable timestamp | The time that the invoice was paid. | | | plan\_id | nullable string | The ID of the plan.

Note that it may not always be present due to things like one-off invoices. | | | status | string | The current status indicator for the invoice. | | | subscription\_id | nullable string | The ID of the subscription. | | | total | nullable decimal | The total value for the invoice. Note that this represents a point-in-time amount for draft invoices and is subject to change. | | | total\_with\_tax | nullable decimal | The total value for the invoice including tax. Note that this amount will be null for draft invoices. | | | amount\_due | nullable decimal | The total amount due for the invoice after credit note and customer balance application. Note that this amount will be null for draft invoices. | | | voided\_at | nullable timestamp | The time the invoice was voided. | | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | | ### Payment Application #### Overview The Payment Application resource represents the application of a successful payment to an invoice. This resource tracks how payments are allocated to invoices, linking each application to the payment attempt that generated it. #### Schema | Field Name | Type | Description | | -------------------- | ------------------ | ------------------------------------------------------------------------------------------------------ | | id | string | The ID of the payment application. | | updated\_at | timestamp | The last updated time of the payment application. | | created\_at | nullable timestamp | The timestamp when the payment application was created. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | | invoice\_id | string | The ID of the invoice this payment application is associated with. | | applied\_at | timestamp | The time when the payment was applied to the invoice. | | currency | string | The currency of the payment application. | | amount | decimal | The amount of the payment that was applied to the invoice. | | payment\_attempt\_id | string | The ID of the payment attempt that resulted in this payment application. | ### Payment Attempt #### Overview The Payment Attempt resource represents each attempt to collect payment. This resource tracks the lifecycle of payment collection efforts, including automatic retries through Orb's dunning process. Each payment attempt captures information about when the attempt was made, the payment provider used, the result of the attempt, and any associated error information. #### Schema | Field Name | Type | Description | | --------------------- | ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | The ID of the payment attempt. | | updated\_at | timestamp | The last updated time of the payment attempt. | | created\_at | nullable timestamp | The timestamp when the payment attempt was created. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | | customer\_id | string | The ID of the customer. | | invoice\_id | nullable string | The ID of the invoice this payment attempt was collecting payment for. | | attempted\_at | timestamp | The time when the payment attempt was made. | | settled\_at | nullable timestamp | The time when the payment was settled, if applicable. | | provider | string | The payment provider used for this attempt. Available options: `stripe`, `adyen`, `orb`. | | initiating\_action | string | The action that initiated this payment attempt. Available options: `auto_collection`, `dunning`, `stripe_user_payment` (for Stripe), `manual_action` (for Orb). | | status | string | The status of the payment attempt. Available options: `started`, `processing`, `failed`, `success`, `canceled`. | | currency | string | The currency of the payment attempt. | | amount | decimal | The amount that was attempted to be collected. | | amount\_captured | nullable decimal | The amount that was successfully captured. | | reference\_id | nullable string | An external reference ID for the payment attempt. For Stripe, this is the PaymentIntent ID. For Orb, this can be a user-provided reference ID. | | payment\_method\_type | nullable string | The type of payment method used for this attempt. Available options: `card`, `us_bank_account`, `link`, `amazon_pay`. | | description | nullable string | A description of the payment attempt. | | error\_details | nullable JSON | Error details returned by the payment processor, if the attempt failed. Contains structured error information including codes and messages. | ### Item #### Overview The Item resource type represents a sellable product or good. Items are associated with all line items, billable metrics, and prices and are used for defining external sync behavior for invoices and tax calculation purposes. #### Schema | Field Name | Type | Description | | --------------------- | --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | | id | string | The ID of the item. | | updated\_at | timestamp | The last updated date of the item. | | created\_at | nullable timestamp | The timestamp when the item was created. | | name | string | The name of the item. | | external\_connections | JSONB
An array of dictionaries with: external\_connection\_name and external\_entity\_id | A list of connections to external third-party providers, including provider names and external entity IDs. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | | archived\_at | nullable timestamp | The time the item was archived. This field will be non-null for archived items. | ### License #### Overview The License resource represents an entitlement provisioned on a subscription. Use this resource with [License Type](#license-type) to reconstruct which entitlements were active on a subscription and when. #### Schema | Field Name | Type | Description | | --------------------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | The ID of the license. | | updated\_at | timestamp | The last updated date of the license. | | created\_at | timestamp | The timestamp when the license was created. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | | subscription\_id | string | The ID of the subscription associated with the license. | | license\_type\_id | string | The ID of the license type associated with the license. | | external\_license\_id | string | The external identifier for the license. If the license has multiple external IDs, this is the sorted, pipe-joined string used by the License API resource. | | start\_time\_inclusive | timestamp | The inclusive timestamp when the license becomes valid. | | end\_time\_exclusive | nullable timestamp | The exclusive timestamp when the license is no longer valid, if defined. | | effective\_start\_time\_inclusive | nullable timestamp | The inclusive timestamp when the license becomes effective for billing calculations. | | effective\_end\_time\_exclusive | nullable timestamp | The exclusive timestamp when the license is no longer effective for billing calculations, if defined. | ### License Type #### Overview The License Type resource represents a named license configuration and the grouping key Orb uses to attribute usage to individual licenses. #### Schema | Field Name | Type | Description | | ------------- | ------------------ | ------------------------------------------------------------------------------------------------------ | | id | string | The ID of the license type. | | updated\_at | timestamp | The last updated date of the license type. | | created\_at | timestamp | The timestamp when the license type was created. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | | grouping\_key | string | The event property Orb uses to attribute usage events to a license for this license type. | | name | string | The user provided name of this license type. | ### Price #### Overview The price resource represents a price that can be associated with a plan, subscription, or invoice. See the [Price configuration](/product-catalog/price-configuration) for more information. The price table is most useful when joining with other tables using the `price_id` field. For example, * you can see `price_id` used in the Daily Line Item Revenue schema above to understand which price configuration generated specific revenue entries. * you can see `price_id` used in the Invoice Line Item Billing schema above to understand which price configuration generated specific invoice line item values. #### Schema | Field Name | Type | Description | | -------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | id | string | The ID of the price. | | updated\_at | timestamp | The last updated date of the price. | | created\_at | timestamp | The timestamp when the price was created. | | name | string | The name of the price. | | external\_price\_id | nullable string | The ID of the price in the external system. | | price\_type | string | The type of price. Can be either "usage\_price" or "fixed\_price". | | cadence | string | The cadence of the price. Can be "one-time", "monthly", "quarterly", "semi-annual", "annual", or "custom". | | billing\_mode | string | The billing mode of the price. Can be "in\_advance" or "in\_arrear". | | billing\_cycle\_duration | nullable decimal | The duration of the billing cycle. | | billing\_cycle\_duration\_unit | string | The unit of the billing cycle duration. Can be "day" or "month". | | invoicing\_cycle\_duration | nullable decimal | The duration of the invoicing cycle. | | invoicing\_cycle\_duration\_unit | nullable string | The unit of the invoicing cycle duration. Can be "day" or "month". | | billable\_metric\_id | nullable string | The ID of the billable metric. This is only present if the price is usage based. | | fixed\_price\_quantity | nullable decimal | The quantity of the price. This is only present if the price is fixed. | | currency | string | The currency of the price. | | conversion\_rate | nullable decimal | The conversion rate of the price. | | item\_id | string | The ID of the item. | | credit\_allocation | nullable JSON | The credit allocation of the price. | | license\_allocations | nullable JSON | The license allocations configured on the price, as an array of objects with `id`, `amount`, `currency`, and `write_off_overage`. | | model\_type | string | The model type of the price. Can be "tiered", "volume", "matrix", "tier\_volume", etc. See [Price configuration](/product-catalog/price-configuration) for the full list. | | rating\_config | JSON | The rating configuration of the price. | | plan\_id | nullable string | The ID of the plan, if the price is associated with a plan. | | plan\_phase\_order | nullable int | The order of the plan phase, if the price is associated with a plan and phase. | | dimensional\_price\_group\_id | nullable string | The ID of the dimensional price group this price represents a partition of, if the price is associated with a dimensional price group. | | dimension\_values | nullable JSON (array of strings) | The dimension values this price partitions the metric by, if the price is associated with a dimensional price group. | | composite\_price\_filters | nullable JSON | The set of filters used to scope eligible prices if this is a composite price. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Price Interval #### Overview The price interval resource represents a time frame during which a price applies to a subscription. [See editing subscriptions](/product-catalog/editing-subscriptions) and [add or edit price intervals](/api-reference/price-interval/add-or-edit-price-intervals) for more information. #### Schema | Field Name | Type | Description | | ---------------------- | ------------------ | ------------------------------------------------------------------------------------------------------ | | id | string | The ID of the price interval. | | updated\_at | timestamp | The last updated date of the price interval. | | created\_at | nullable timestamp | The timestamp when the price interval was created. | | subscription\_id | string | The ID of the subscription. | | customer\_id | string | The ID of the customer. | | price\_id | string | The ID of the price. | | start\_date\_inclusive | timestamp | The start date of the price interval. | | end\_date\_exclusive | nullable timestamp | The end date of the price interval, if defined. | | billing\_cycle\_day | number | The day of the billing cycle. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ### Subscription version #### Overview The subscription version resource represents the subscription schedule for a given subscription, allowing for an understanding of underlying plan transitions. A given subscription may have multiple subscription versions mapped to it to track plan version changes and more. #### Schema | Field Name | Type | Description | | ------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------ | | id | string | The ID of the subscription version. | | updated\_at | timestamp | The last updated date of the subscription version. | | created\_at | nullable timestamp | The timestamp when the subscription version was created. | | subscription\_id | string | The ID of the subscription. | | customer\_id | string | The ID of the customer. | | plan | JSON | The plan information associated with this subscription version. | | start\_date | timestamp | The time when this subscription version started. | | ended\_at | nullable timestamp | If set, the time when this subscription version ended. | | subscription\_start\_date | timestamp | the timestamp when this subscription started. | | subscription\_ended\_at | nullable timestamp | If set, the time when this subscription ended. | | modified\_at | timestamp | The time when this resource was last modified in Orb's system. | | deleted\_at | nullable timestamp | Deletion marker for this resource. If not null, then the resource has been deleted as of the time set. | ## Legacy resources (deprecated) Orb periodically deprecates older data export resources that no longer meet the product and infrastructure standards we require to support accurate reporting, scalable workloads, or new functionality. What you can expect from end of service: * **This resource will no longer appear in the in-app UI**, including the configuration table in settings. * **It will no longer be configurable for new connections**—customers cannot create or enable new exports using the deprecated resource. * **Existing connections will stop syncing**, meaning no new or updated data will flow to destinations after the deprecation date. Previously synced data will remain in your data warehouse. | Resource type | Deprecation date | Alternative resources | | ----------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [Invoice](https://docs.withorb.com/api-reference/invoice/fetch-invoice) | Nov 26, 2025 | [Invoice metadata](https://docs.withorb.com/data-exports/resource-types#invoice-metadata), [Payment attempt](https://docs.withorb.com/data-exports/resource-types#payment-attempt), [Payment application](https://docs.withorb.com/data-exports/resource-types#payment-application)

Note: the [Invoice API endpoint](https://docs.withorb.com/api-reference/invoice/fetch-invoice) remains fully servicable for non-reporting applications. | # S3 Source: https://docs.withorb.com/data-exports/s3 ## Prerequisites * [ ] By default, S3 authentication uses role-based access. You will need the trust policy prepopulated with the data syncing service's identifier to grant access. It should look similar to the following JSON object with a proper service account identifier: ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": [ "sts:AssumeRoleWithWebIdentity" ], "Principal": { "Federated": "accounts.google.com" }, "Condition": { "StringEquals": { "accounts.google.com:oaud": "", "accounts.google.com:sub": "" } } } ] } ``` ## Step 1: Set up destination S3 bucket ### Create bucket 1. Navigate to the **S3** service page. 2. Click **Create bucket**. 3. Enter a **Bucket name** and modify any of the default settings as desired. Note: **Object Ownership** can be set to "ACLs disabled" and **Block Public Access settings for this bucket** can be set to "Block all public access" as recommended by AWS. Make note of the **Bucket name** and **AWS Region**. 4. Click **Create bucket**. > 📘 Recommendation: dedicated bucket for data transfers > > Use a unique bucket for these transfers. This: > > * Prevents resource contention with other workloads > * Avoids accidental data loss from mixed lifecycle or cleanup rules > * Improves security by reducing surface area and enabling tighter, destination-scoped policies > 🧹 **Optional: Add a short retention lifecycle policy** > > You may configure a lifecycle rule on the staging bucket to automatically delete objects older than 2 days as the bucket is not used to persist data. In the bucket **Management** tab, click **Create lifecycle rule**, set an expiration action for current versions of objects with a 2-day age. Note that transfer logic automatically cleans up files after transfer completion, so this is an optional step. ## Step 2: Create policy and IAM role ### Create policy 1. Navigate to the **IAM** service page. 2. Navigate to the **Policies** navigation tab, and click **Create policy**. 3. Click the **JSON** tab, and paste the following policy, being sure to replace `BUCKET_NAME` with the name of the bucket chosen in Step 1. ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": ["s3:PutObject", "s3:DeleteObject"], "Resource": "arn:aws:s3:::BUCKET_NAME/*" } ] } ``` > 📘 Understanding the s3:DeleteObject requirement > > By default, a connection test is performed against the destination during initial configuration and `s3:DeleteObject` is required to clean up test artifacts. Once the test has been performed successfully and the destination added, this action can be safely removed, as S3 destinations are append-only by default. > 🔐 **KMS encryption (optional)** > > If your S3 destination bucket uses KMS encryption (CMK), add the following statement to the `Statement` array of your IAM policy to allow data encryption/decryption with your KMS key. Encryption with SSE-C is not currently supported. > > ```json theme={null} > { > "Effect": "Allow", > "Action": [ > "kms:GenerateDataKey", > "kms:Decrypt" > ], > "Resource": "arn:aws:kms:REGION_NAME:ACCOUNT_ID:key/KEY_ID" > } > ``` > > Replace `REGION_NAME`, `ACCOUNT_ID`, and `KEY_ID` with your values. 4. Click **Next: Tags**, click **Next: Review**. 5. Name the policy, add a description, and click **Create policy**. ### Create role 1. Navigate to the **IAM** service page. 2. Navigate to the **Roles** navigation tab, and click **Create role**. 3. Select **Custom trust policy** and paste the provided trust policy to allow AssumeRole access to the new role. Click **Next**. 4. Add the permissions policy created above, and click **Next**. 5. Enter a **Role name**, for example, `transfer-role`, and click **Create role**. 6. Once successfully created, search for the created role in the Roles list, click the role name, and make a note of the **ARN** value. > 📘 Alternative authentication method: AWS User with HMAC Access Key ID & Secret Access Key > Role based authentication is the preferred authentication mode for S3 based on AWS recommendations. However, HMAC Access Key ID & Secret Access Key is an alternative authentication method that can be used if preferred. > > 1. Navigate to the **IAM** service page. > 2. Navigate to the **Users** navigation tab, and click **Add users**. > 3. Enter a **User name** for the service, for example, `transfer-service`, click **Next**. Under **Select AWS access type**, select the **Access key - Programmatic access** option. Click **Next: Permissions**. > 4. Click the **Attach existing policies directly** option, and search for the name of the policy created in the previous step. Select the policy, and click **Next: Tags**. > 5. Click **Next: Review** and click **Create user**. > 6. In the **Success** screen, record the **Access key ID** and the **Secret access key**. ## Step 3: Add your destination Securely connect your system to Orb using the Data Export UI under Settings -> Data Exports. ## Permissions checklist * IAM policy on the role allows: * `s3:PutObject` on `arn:aws:s3:::BUCKET_NAME/*` * `s3:DeleteObject` on `arn:aws:s3:::BUCKET_NAME/*` (only required for initial connection test; may be removed after setup) * If using KMS encryption (CMK), IAM policy also allows: * `kms:GenerateDataKey` and `kms:Decrypt` on your CMK ARN * Bucket exists in the intended region; folder prefix (if any) is configured as desired * Trust policy allows the data transfer service to assume the role ## FAQ ### Q: How is the S3 connection secured? **A:** The recommended approach is role-based access using an IAM Role with a scoped permissions policy. The role is assumed via a trust policy and short-lived credentials, so no long-lived access keys are required. Optionally, access can be configured with HMAC access keys if your policies require it. For at-rest encryption, S3-managed encryption or KMS CMKs are supported (see the KMS callout above for required actions). Grant only the minimum permissions needed (PutObject, and DeleteObject for initial connection test). ### Q: What are the `oaud` vs `sub` IDs used for? **A:** These are identity claims used in the IAM trust policy when federating from GCP to AWS. `sub` uniquely identifies our Google principal in federation. `oaud` is an additional claim used to bind role assumption to your organization. ### Q: How is data organized in the bucket? **A:** Data lands in Hive-style partitions per model: `//dt=/_.`. You can set `` during configuration. ### Q: What file formats are supported? **A:** Parquet (default/recommended), CSV, and JSON/JSONL. ### Q: How are large datasets written? **A:** Files are automatically split; multiple files may be written per model per transfer. ### Q: How do I know when a transfer completed? **A:** Each transfer writes a manifest file per model under `_manifests`. The `_manifests` folder is created automatically at the root of the bucket. Files are written per model per transfer in the following format: `_manifests//dt=/manifest_{transfer_id}.json`. ### Q: Why do I sometimes see duplicates? **A:** Object storage is append-only. The change detection process uses a lookback window to ensure no data is missed, which can create duplicates. Downstream pipelines should deduplicate on primary keys prioritizing the most recent transfer window; manifest files can help bound the set of files to read. # Snowflake Source: https://docs.withorb.com/data-exports/snowflake ## Prerequisites * [ ] Locate your Public Key generated on your behalf. The Public Key will be a long string of text, loosely resembling the format: `'MIIBI......Xrw2nwIDAQAB'` * [ ] In order to complete the following setup steps, you or a Snowflake admin on your team must have the securityadmin and sysadmin roles. (To check your account for these roles, run `SHOW GRANTS TO USER ;` and review the `role` column.) * [ ] If your Snowflake data warehouse is using Snowflake Access Policies, you will need to have the data syncing service's static IP available to complete Step 2. > 📘 **Recommendation: Key-pair authentication with service user** > > Snowflake is deprecating single-factor passwords and will disallow passwords for service users (TYPE=SERVICE) by October 2026. For that reason, we strongly recommend configuring the transfer user as a service user with key-pair authentication. ## Step 1: Create role, user, warehouse, and database in the data warehouse 1. Review and make any changes to the following setup script. ```sql theme={null} begin; -- create variables for user / role / warehouse / database set user_name = 'TRANSFER_USER'; -- all letters must be uppercase set role_name = 'TRANSFER_ROLE'; -- all letters must be uppercase set warehouse_name = 'TRANSFER_WAREHOUSE'; -- all letters must be uppercase set database_name = 'TRANSFER_DATABASE'; -- all letters must be uppercase -- change role to securityadmin for user / role steps use role securityadmin; -- create role for data transfer service create role if not exists identifier($role_name); grant role identifier($role_name) to role SYSADMIN; -- establish SYSADMIN as the parent of the new role. Note: this does not grant the access privileges of SYSADMIN to the new role. -- create a user for data transfer service create user if not exists identifier($user_name) RSA_PUBLIC_KEY='MIIBIjANBgkqh...'; -- replace with the complete public key as required in the prerequisite -- set default role and warehouse to new user alter user identifier($user_name) SET default_role = $role_name; alter user identifier($user_name) SET default_warehouse = $warehouse_name; alter user identifier($user_name) SET type = service; grant role identifier($role_name) to user identifier($user_name); -- change role to sysadmin for warehouse / database steps use role sysadmin; -- create a warehouse for data transfer service create warehouse if not exists identifier($warehouse_name) warehouse_size = xsmall warehouse_type = standard auto_suspend = 60 auto_resume = true initially_suspended = true; -- create database for data transfer service create database if not exists identifier($database_name); -- grant service role access to warehouse grant USAGE on warehouse identifier($warehouse_name) to role identifier($role_name); -- grant service access to database grant CREATE SCHEMA, MONITOR, USAGE on database identifier($database_name) to role identifier($role_name); commit; ``` > 🚧 **Alternative authentication method: username & password** > > By default, this script creates a new user using key-pair authentication. If you'd prefer to use username & password authentication, instead of: > > ```sql theme={null} > create user if not exists identifier($user_name) > RSA_PUBLIC_KEY='MIIBIjANBgkqh...'; > ``` > > Use the following block: > > ```sql theme={null} > create user if not exists identifier($user_name) > password = 'some_password'; > ``` > 📘 **Using an existing `schema`** > > By default, a new schema (with a name you provide) will be created in the target Snowflake database upon the initial connection. If instead you create the `schema` ahead of time, you may remove the `CREATE SCHEMA` permission, and instead `grant ALL PRIVILEGES` on the target `schema` for the designated `role`. > > The script below can be used to complete this step: > > ```sql theme={null} > set role_name = 'TRANSFER_ROLE'; > set database_name = 'TRANSFER_DATABASE'; > set schema_name = 'PRECREATED_SCHEMA'; > > use database identifier($database_name); > grant ALL PRIVILEGES on schema identifier($schema_name) to role identifier($role_name); > ``` > 📘 **Using an existing `warehouse` or `database`** > > By default, this script creates a new warehouse and a new database. If you'd prefer to use an existing warehouse/database, change the `warehouse_name` variable from `TRANSFER_WAREHOUSE` to the name of the warehouse to be shared/`database_name` variable from `TRANSFER_DATABASE` to the name of the database to be shared. 2. In the Snowflake interface, select the dropdown next to the "Run" button, and click **Run All**. This will run every query in the script at once. If successful, you will see `Statement executed successfully` in the query results. ## Step 2: Configure the Snowflake access policy If your Snowflake data warehouse is using Snowflake Access Policies, a new policy must be added to allow the data syncing service's static IP to write to the warehouse. 1. Review current network policies to check for existing IP safelists. ```sql theme={null} SHOW NETWORK POLICIES; ``` 2. If there is no existing Snowflake Network Policies (the `SHOW` query returns no results), you can skip to Step 3. 3. If there is an existing Snowflake Network Policy, you must alter the existing policy or create a new one to safelist the data syncing service's static IP address. Use the `CREATE NETWORK POLICY` command to specify the IP addresses that can access your Snowflake warehouse. ```sql theme={null} CREATE NETWORK POLICY ALLOWED_IP_LIST = ('35.192.85.117'); ``` > ❗️**Creating your first network policy** > > If you have no existing network policies and you create your first as part of this step, all other IPs outside of the `ALLOWED_IP_LIST` will be blocked. Snowflake does not allow setting a network policy that blocks your current IP address. (An error message results while trying to create a network policy that blocks the current IP address.) But be careful when setting your first network policy. ## Step 3: Add your destination Securely connect your system to Orb using the Data Export UI under Settings -> Data Exports. ## Permissions checklist * Role grants: * `USAGE` on the target warehouse * If the destination schema will be created by the service: * `USAGE` and `CREATE SCHEMA` on the target database (the setup script also includes `MONITOR`) * If using a pre-created schema: * `USAGE` on the target database * `ALL PRIVILEGES` on the target schema * User defaults set (optional but recommended): `DEFAULT_ROLE`, `DEFAULT_WAREHOUSE` * If using key auth: user has the PKCS#8 `RSA_PUBLIC_KEY` set * If network policies are enforced: our egress IP is allowlisted > 🚧 **Avoid `FUTURE GRANTS` on ownership to non-transfer Snowflake roles** > > The transfer role must retain ownership of the tables and internal stages it creates. Please ensure `FUTURE OWNERSHIP` on tables in the destination schema is not granted to other roles in Snowflake. ## FAQ ### Q: How is the Snowflake connection secured? **A:** We recommend key-based authentication. You register a public key on a Snowflake user and we authenticate using the corresponding private key, so no password is shared or stored. You can also enforce Snowflake Network Policies to allowlist our egress IP. ### Q: What permissions does the data transfer role need? **A:** Minimum grants: * `USAGE` on the warehouse * If the destination schema will be created by the service: `USAGE` and `CREATE SCHEMA` on the database * If using a pre-created schema: `USAGE` on the database and `ALL PRIVILEGES` on the schema ### Q: Can I use an existing warehouse? **A:** Yes. Grant `USAGE` on that warehouse to the transfer role. You may also size the warehouse to control performance/cost. ### Q: Should I include the `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` tags in the public key when adding it to Snowflake? **A:** No, you should only provide the raw public key string, without the `-----BEGIN PUBLIC KEY-----` and `-----END PUBLIC KEY-----` tags. ### Q: Can I use the same transfer role for multiple destinations? **A:** If you have multiple Snowflake destinations, you can use the same transfer role for up to 2 destinations. You will be issued a unique public key for each destination and can use the `RSA_PUBLIC_KEY` and `RSA_PUBLIC_KEY_2` to store up to two public keys. # Snowflake password deprecation Source: https://docs.withorb.com/data-exports/snowflake-password-deprecation ### \*\*By March 2026, all Snowflake `SERVICE` connections must authenticate with Key Pair authentication. \*\* In late 2024, Snowflake introduced a new property named `TYPE` for the `USER` object. The value of this property (`null`, `HUMAN`, `SERVICE`, or `LEGACY_SERVICE`) determines the permitted authentication methods for each user. Authentication requirements associated with the user type will become progressively stricter throughout 2025 and 2026. To maintain uninterrupted operation of your Snowflake connection, follow the recommended steps outlined here. These steps will help you identify your service user's current configuration and, if necessary, apply either a temporary workaround or a long-term solution.
## Understanding your current connection configuration If your user was configured using the suggested setup script from \~2024 or earlier, the `TYPE` parameter is likely currently set to `null`. You can verify this using the following query: ```sql Snowflake theme={null} DESCRIBE USER ; -- check TYPE, RSA_PUBLIC_KEY ``` In Snowflake, the `TYPE` of `null` will be treated synonymously as if the `TYPE` is `PERSON`, and single factor password authentication will eventually be deprecated. * If your `TYPE` is set to `null` or `HUMAN`, you must apply either the Long term fix (preferred) or Temporary fix in this document. * If your `RSA_PUBLIC_KEY` is set and `TYPE` is set to `SERVICE`, no further action is required. > ❗️ `HUMAN` or `null` user migration deadline > > `HUMAN` or `null` users will be unable to login with single factor passwords starting in March 2026. (See [Snowflake guidance](https://docs.snowflake.com/en/user-guide/security-mfa-rollout#label-security-mfa-milestone-human-all))
## Long term fix: Update Snowflake authentication method to Key Pair,` TYPE` to `SERVICE` Snowflake will eventually enforce all non-human accounts to be `TYPE = SERVICE`, and our recommended authentication method is Key Pair authentication. The following steps will guide this migration: ### Prerequisite: Locate your Public Key generated on your behalf The key should resemble the following text: ``` 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvBSY419n5K4ASO0y6oa02z37Ygaq3kUZrENgoq2b3H9El9eANzdfs5i9b7OGDn4PGw9162cHmUosUByz5vZF3yjBeKPaGt/YfebmiejvzcFBS0bwpuPNik5jmVoX0tVStpNn0R47H0nabyXBxgRspKGVTmTLOhuRpoqbwYuV/ayYZ35UL05D0j+NwjEJMObWRKKxyCMgDxmClSPUZpJ7iceahTdIWQoUqH14l3isedZthKAVZkA+SxGQmCmTDwaFz5VgHLZ0d4vX3gMCS3lUK7axJDWjsWCJ1WNMXkE7rBpyZdpuH6xuALvKs9yqhX1qYdNgh9P735mISzISXrw2nwIDAQAB'; ``` If you need help locating this key, please contact support. ### Step 1: Alter user authentication to use Key Pair ```sql Snowflake theme={null} ALTER USER SET RSA_PUBLIC_KEY='MIIBI...THIS_IS_AN_EXAMPLE_AND_SHOULD_BE_REPLACED_WITH_YOUR_KEY...n5K4ASO0y6oa02z37Ygaq3kUZrENgoq2b3H9El9eANzdfs5i9b7OGDn4PGw9162cHmUosUByz5vZF3yjBeKPaGt/YfebmiejvzcFBS0bwpuPNik5jmVoX0tVStpNn0R47H0nabyXBxgRspKGVTmTLOhuRpoqbwYuV/ayYZ35UL05D0j+NwjEJMObWRKKxyCMgDxmClSPUZpJ7iceahTdIWQoUqH14l3isedZthKAVZkA+SxGQmCmTDwaFz5VgHLZ0d4vX3gMCS3lUK7axJDWjsWCJ1WNMXkE7rBpyZdpuH6xuALvKs9yqhX1qYdSXrw2nwIDAQAB'; ``` At this point, we recommend testing your connection to ensure the Key Pair was configured correctly. ### Step 2: Alter User `TYPE` to `SERVICE` Once the connection test has succeeded and the authentication method has been updated, you may alter the user `TYPE` to `SERVICE`. You may also wish to unset the preexisting password. ```sql Snowflake theme={null} ALTER USER SET TYPE = SERVICE; DESCRIBE USER ; -- to verify ALTER USER UNSET PASSWORD; -- optional ```
## Temporary fix: Alter User `TYPE` to `LEGACY_SERVICE` In the near term, Snowflake has provided a temporary resolution to allow for uninterrupted operation of existing Snowflake service accounts for slightly longer than the long term fix. You can temporarily extend your migration timeline by altering your user to indicate that it is a legacy service user using the following query: ```sql Snowflake theme={null} LTER USER SET TYPE = LEGACY_SERVICE; DESCRIBE USER ; -- to verify ``` > ❗️ `LEGACY_SERVICE` user type migration timeline > > `LEGACY_SERVICE` users will be deprecated starting in June 2026. (See [Snowflake guidance](https://docs.snowflake.com/en/user-guide/security-mfa-rollout#label-security-mfa-milestone-service-all)) # Financial analyses using Orb data Source: https://docs.withorb.com/data-exports/use-cases Create your own financial reports or analyses using the data provided by Orb. Here are some common reports and how to get started in Orb. ## Quick reference: Resources by use case Use this table to determine which resources to enable for your reporting needs: | Use case | Required resources | Notes | | ----------------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------------ | | **Billings/AR reporting** | `invoice_metadata`, `credit_note`, `customer_balance_transaction` | Add `invoice_line_item_billing` for line-item detail | | **Revenue recognition** | `daily_line_item_revenue`, `credit_ledger_entry`, `credit_note_line_item` | Use `_event` variants if respecting accounting period locks | | **Collections/AR aging** | `invoice_metadata`, `payment_attempt`, `payment_application` | See also [AR aging](/financial-operations/ar-aging) in Orb | | **Customer/subscription analytics** | `customer`, `subscription`, `subscription_version`, `plan` | Add `price_interval` for pricing history | | **Discount analysis** | `adjustment`, `adjustment_interval`, `coupon`, `coupon_redemption` | Join with `invoice_line_item_billing` to see applied amounts | | **Credit tracking** | `credit_ledger_entry` | Use `_event` variant for accounting period compliance | **Base vs Event resources**: Resources ending in `_event` (like `daily_line_item_revenue_event`) include `lock_adjusted_timestamp` for [accounting period lock](/revenue-reporting/revenue-recognition#actions-in-the-past-and-accounting-period-locks) compliance. Use these when your finance team closes accounting periods in Orb. If you issue invoices to customers in more than one currency, make sure you group by or filter to a specific `invoicing_currency` to avoid adding numbers together that reference different currencies. For example, you can’t sensibly add billing or revenue numbers together for JPY and USD currencies. ## Billings reports The following resource types can help you understand invoice details and line item values for invoices that have been issued / billed. | Resource type | Description | | ---------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `invoice_line_item_billing` or `invoice_line_item_billing_event` | Contains the individual line item values for invoices that have been issued / billed.

`invoice_line_item_billing_event` contains additional fields to enable recognition with respect to accounting period locks. | | `invoice_metadata` | Contains data for every invoice in Orb, including draft and voided invoices. Contains the high level information about the invoice including status, customer, total, invoice number, and more. Credit notes are not included. | | `invoice` | Contains data for all non-draft invoices. Contains broken out information about line items, adjustments, and address information in JSON fields. In general, we recommend combining `invoice_metadata` with other resources instead of using the `invoice` resource. | ### Net billings To calculate net billings for a specific period, sum the total from invoices sent in the period, less the total from credit notes. | Resource type | Task | Steps | | ------------------------------ | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `invoice_metadata` | Sum invoice amounts |
  1. Filter out voided invoices using `voided_at` and filter out deleted invoices using `deleted_at`
  2. Filter to the desired date range using `invoice_date`
  3. Sum the `total`
| | `credit_note_line_item` | Remove credit note amounts |
  1. Filter out voided credit notes using `voided_at` and filter out deleted credit notes using `deleted_at`
  2. Filter to the desired invoice using `invoice_id`
  3. Sum the `total`
| | `customer_balance_transaction` | Remove applied customer balances |
  1. Filter to `action = 'applied_to_invoice'`
  2. Filter out deleted transactions using `deleted_at`
  3. Handle both decrements (credit applied) and increments (negative balance applied)
| ```sql Combined query {9-10,16,25,30-31,38,45} [expandable] theme={null} WITH relevant_invoices AS ( SELECT id , invoice_date , total FROM invoice_metadata AS invoice WHERE invoice.status IN ('issued', 'paid', 'synced') AND invoice.deleted_at IS NULL AND invoice.voided_at IS NULL ) , aggregate_invoices AS ( SELECT DATE_TRUNC('month', invoice_date) AS invoice_month , COUNT(*) AS cnt , SUM(total) AS total FROM relevant_invoices AS invoice GROUP BY 1 ) , aggregate_credit_notes AS ( SELECT DATE_TRUNC('month', invoice.invoice_date) AS invoice_month , SUM(CAST(credit_note.total AS DECIMAL)) AS total FROM relevant_invoices AS invoice LEFT JOIN credit_note ON credit_note.invoice_id = invoice.id WHERE credit_note.voided_at IS NULL AND credit_note.deleted_at IS NULL GROUP BY 1 ) , aggregate_balance_transactions AS ( -- Balance transactions can either reduce or increase invoice amount due: -- - type='decrement' with action='applied_to_invoice': customer credit used (reduces amount due) -- - type='increment' with action='applied_to_invoice': negative balance applied (increases amount due) SELECT DATE_TRUNC('month', invoice.invoice_date) AS invoice_month , SUM(CASE WHEN cbt.type = 'decrement' THEN cbt.amount -- Credit applied (subtract from billings) WHEN cbt.type = 'increment' THEN -cbt.amount -- Negative balance (add to billings) ELSE 0 END) AS total FROM relevant_invoices AS invoice LEFT JOIN customer_balance_transaction AS cbt ON cbt.invoice_id = invoice.id WHERE cbt.action = 'applied_to_invoice' AND cbt.deleted_at IS NULL GROUP BY 1 ) SELECT invoice.invoice_month , invoice.cnt AS count , (invoice.total - COALESCE(credit_note.total, 0) - COALESCE(balance_transaction.total, 0)) AS total FROM aggregate_invoices AS invoice LEFT JOIN aggregate_credit_notes AS credit_note ON invoice.invoice_month = credit_note.invoice_month LEFT JOIN aggregate_balance_transactions AS balance_transaction ON invoice.invoice_month = balance_transaction.invoice_month ORDER BY 1 ASC ``` ### Invoices by status View invoice totals by [invoice status](/invoicing/structure#invoice-states) to view collections, refunds, and pending balances. | Resource type | Task | Steps | | ------------------ | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `invoice_metadata` | Filter to issued invoices |
  1. Filter our voided and deleted invoices used `voided_at` and `deleted_at` respectively.
  2. Filter to desired date range using `due_date` or `invoice_date`
| | `invoice_metadata` | Group by status | Group by `status` to get an associated breakdown | ```sql Filter to issued invoices {1,10-11} theme={null} SELECT DATE_TRUNC('month', invoice.invoice_date) , count(*) AS count , SUM(invoice.total) AS total FROM invoice_metadata AS invoice -- The invoice statuses for issued invoices WHERE invoice.status IN ('issued', 'paid', 'synced') AND invoice.deleted_at IS NULL AND invoice.voided_at IS NULL GROUP BY 1 ORDER BY 1 ASC ``` ```sql Group by status {1,7-8} theme={null} SELECT invoice.status , count(*) AS count , SUM(invoice.total) AS total FROM invoice_metadata AS invoice WHERE invoice.deleted_at IS NULL AND invoice.voided_at IS NULL GROUP BY 1 ORDER BY 1 ASC ``` ### AR Aging To understand days outstanding for invoices issued through Orb to power your collections workflow, group issued invoices that have not been paid by number of days outstanding. This report only works for customers invoicing through Orb. If you’re syncing invoices from Orb to a third party system, you’ll need to leverage that system’s reporting capabilities to understand invoices that are past due. | Resource type | Task | Steps | | ------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------------------ | | `invoice_metadata` | Filter to relevant invoices |
  1. Filter to `issued` or `synced` status
  2. Filter out voided and deleted invoices
| | `invoice_metadata` | Group invoices by days outstanding | Use `due_date` to bucket invoices into aging groups: Current, 1-30, 31-60, 61-90, 91+ days past due | ```sql Generate an AR Aging report [expandable] theme={null} WITH unpaid_invoices AS ( SELECT id , due_date , customer_id , total , CAST(amount_due AS DECIMAL) AS amount_due FROM invoice_metadata -- Include issued and synced invoices (unpaid) WHERE status IN ('issued', 'synced') AND deleted_at IS NULL AND voided_at IS NULL ) , invoices_with_buckets AS ( SELECT id , customer_id , total , amount_due , CASE WHEN due_date >= NOW() THEN 'Current' WHEN due_date >= NOW() - INTERVAL '30 days' THEN '1-30 days past due' WHEN due_date >= NOW() - INTERVAL '60 days' THEN '31-60 days past due' WHEN due_date >= NOW() - INTERVAL '90 days' THEN '61-90 days past due' ELSE '91+ days past due' END AS aging_bucket FROM unpaid_invoices ) SELECT aging_bucket , customer.name , COUNT(*) AS invoice_count , SUM(total) AS total , SUM(amount_due) AS amount_due FROM invoices_with_buckets LEFT JOIN customer ON customer.id = invoices_with_buckets.customer_id GROUP BY 1, 2 ORDER BY CASE aging_bucket WHEN 'Current' THEN 1 WHEN '1-30 days past due' THEN 2 WHEN '31-60 days past due' THEN 3 WHEN '61-90 days past due' THEN 4 WHEN '91+ days past due' THEN 5 END , 2 ASC ``` ## Revenue recognition reports The following resource types can help you understand how to recognize your revenue. | Resource type | Description | | ------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `daily_line_item_revenue` or `daily_line_item_revenue_event` | Contains data for all invoice line items per day, including quantity and rate details.

`daily_line_item_revenue_event` contains additional fields to enable recognition with respect to accounting period locks. | | `credit_ledger_entry` or `credit_ledger_entry_event` | Contains data for all credit ledger entries.

`credit_ledger_entry_event` contains additional fields to enable recognition with respect to accounting period locks. | | `credit_note_line_item` or `credit_note_line_item_event` | Contains data for credit note amounts broken down by line item.

Today we don’t expose daily revenue amounts for credit note line items, so these amounts will need to be prorated across the relevant invoice line item period.

`credit_note_line_item_event` contains additional fields to enable recognition with respect to accounting period locks. | When analyzing revenue data, there are a few gotchas: * Where relevant, make sure you analyze *all* sources that contribute to revenue amounts (the three sources listed above). Leaving out any of these resources of revenue data may lead to incorrect numbers. * If you issue invoices to customers in more than one currency, make sure you group by currency to avoid adding numbers together that reference different currencies (e.g. you can’t add revenue numbers together for JPY and USD). ### Recognized revenue To understand how much revenue to recognize based on the invoice line items, you can leverage the `daily_line_item_revenue` resource. | Resource type | Task | Steps | | ------------------------- | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `daily_line_item_revenue` | Filter to relevant line items |
  1. Filter out reverted entries with `reverted_at`
  2. Filter to the desired date range using `timeframe_start` and `timeframe_end`
| | `daily_line_item_revenue` | Sum the revenue amounts | Sum `rounded_amount` to get a final revenue number | Alternatively, to recognize revenue with respect to the [accounting period lock posture in Orb](/revenue-reporting/revenue-recognition#actions-in-the-past-and-accounting-period-locks), leverage the `daily_line_item_revenue_event` resource. | Resource type | Task | Steps | | ------------------------------- | ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `daily_line_item_revenue_event` | Filter to relevant line items | Filter to the desired date range using `lock_adjusted_timestamp` | | `daily_line_item_revenue_event` | Sum the revenue amounts | Sum `rounded_amount`, treating entries with `is_revert = False` as positive and `is_revert = True` as negative, to get a final revenue number. | ```sql Aggregate revenue using daily_line_item_revenue {9-11,17-18} [expandable] theme={null} WITH daily_line_items AS ( SELECT timeframe_start , invoicing_currency , rounded_amount FROM daily_line_item_revenue -- We are interested in our revenue for calendar year 2024 WHERE CAST(timeframe_start AT TIME ZONE 'UTC' AS DATE) >= '2024-01-01' AND CAST(timeframe_start AT TIME ZONE 'UTC' AS DATE) < '2025-01-01' AND reverted_at IS NULL -- We want to filter out partial invoices to avoid counting revenue multiple times AND is_partial_invoice IS FALSE ) SELECT DATE_TRUNC('month', timeframe_start AT TIME ZONE 'UTC') AS month , invoicing_currency AS currency , SUM(rounded_amount) AS rounded_amount FROM daily_line_items GROUP BY 1, 2 ORDER BY 1 ASC NULLS FIRST ``` ```sql Aggregate revenue using daily_line_item_revenue_event {9-11,18-22} [expandable] theme={null} WITH daily_line_items AS ( SELECT lock_adjusted_timestamp , invoicing_currency , rounded_amount , is_revert FROM daily_line_item_revenue_event -- We are interested in our revenue for calendar year 2024 WHERE CAST(lock_adjusted_timestamp AT TIME ZONE 'UTC' AS DATE) >= '2024-01-01' AND CAST(lock_adjusted_timestamp AT TIME ZONE 'UTC' AS DATE) < '2025-01-01' -- We want to filter out partial invoices to avoid counting revenue multiple times AND is_partial_invoice IS FALSE ) SELECT DATE_TRUNC('month', timeframe_start AT TIME ZONE 'UTC') AS month , invoicing_currency AS currency -- We need to handle revert entries appropriately , SUM(CASE WHEN (is_revert = TRUE) THEN (rounded_amount * -1) ELSE rounded_amount ) AS rounded_amount FROM daily_line_items GROUP BY 1, 2 ORDER BY 1 ASC NULLS FIRST ``` Orb also provides a breakdown of each individual step in the invoicing process to allow for precise analysis of [invoicing calculations](https://docs.withorb.com/invoicing/invoice-calculations). The documentation linked provides a great breakdown, but a quick summary is: | Value | Field Name | Description | | ------------------------- | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | List price / subtotal | `subtotal` | The original price before any adjustment, prepaid credits, or otherwise. | | Adjusted subtotal | `adjusted_subtotal` | The list price minus any adjustments. If no adjustments have been applied, this value is the same as the list price. | | Credits applied | `credits_applied` | The amount of prepaid credits applied to this line item. If no prepaid credits have been applied, this value is 0. | | Conversion rate | `conversion_rate` | The rate at which usage in a virtual currency is converted to a real currency (e.g. from credits to USD). If the invoicing currency and the pricing currency are the same, the `conversion_rate_` is 1. | | Partially invoiced amount | `partially_invoiced_amount` | The amount that has been invoiced already through a partial invoice. If no partial invoices have been issued, this value is 0. | | Amount | `amount` | The amount of the line item before any final rounding. | | Rounded amount | `rounded_amount` | The amount of the line item after rounding and before taxes, if applicable, are applied. | ```sql Breaking down each contribution to the final amount [expandable] theme={null} WITH daily_line_items AS ( SELECT * FROM daily_line_item_revenue WHERE CAST(timeframe_start AT TIME ZONE 'UTC' AS DATE) >= '2025-01-01' AND CAST(timeframe_start AT TIME ZONE 'UTC' AS DATE) < '2025-02-01' AND reverted_at IS NULL ) SELECT CAST(timeframe_start AT TIME ZONE 'UTC' AS DATE) , count(*) AS num_line_items -- Step 1: list price , SUM(subtotal) AS list_price -- Step 2: adjusted subtotal , SUM(adjusted_subtotal) AS list_price_minus_adjustments -- Step 3: track number of prepaid credits applied , SUM(credits_applied) AS credits_applied -- Step 4: subtract prepaid credits and convert , SUM((adjusted_subtotal - credits_applied) * conversion_rate) AS post_prepaid_credits -- Step 5: subtract any partial invoices to get the amount before rounding (can be computed two different ways) , SUM((adjusted_subtotal - credits_applied) * conversion_rate - partially_invoiced_amount) AS amount_complex , SUM(amount) AS amount_simple -- Step 6: any final rounding occurs , SUM(rounded_amount) AS rounded_amount FROM daily_line_items GROUP BY 1 ORDER BY 1 ASC ``` ```sql Breakdown using daily_line_item_revenue_event [expandable] theme={null} WITH daily_line_items AS ( SELECT * FROM daily_line_item_revenue_event WHERE CAST(lock_adjusted_timestamp AT TIME ZONE 'UTC' AS DATE) >= '2025-01-01' AND CAST(lock_adjusted_timestamp AT TIME ZONE 'UTC' AS DATE) < '2025-02-01' ) SELECT CAST(timeframe_start AT TIME ZONE 'UTC' AS DATE) , SUM(CASE WHEN (is_revert = TRUE) THEN -1 ELSE 1 ) as num_line_items -- Step 1: list price , SUM(CASE WHEN (is_revert = TRUE) THEN (subtotal * -1) ELSE subtotal ) AS list_price -- Step 2: adjusted subtotal , SUM(CASE WHEN (is_revert = TRUE) THEN (adjusted_subtotal * -1) ELSE adjusted_subtotal ) AS list_price_minus_adjustments -- Step 3: track number of prepaid credits applied , SUM(CASE WHEN (is_revert = TRUE) THEN (credits_applied * -1) ELSE credits_applied ) AS credits_applied -- Step 4: subtract prepaid credits and convert , SUM(CASE WHEN (is_revert = TRUE) THEN (((adjusted_subtotal - credits_applied) * conversion_rate) * -1) ELSE (adjusted_subtotal - credits_applied) * conversion_rate ) AS post_prepaid_credits -- Step 5: subtract any partial invoices to get the amount before rounding (can be computed two different ways) , SUM(CASE WHEN (is_revert = TRUE) THEN (((adjusted_subtotal - credits_applied) * conversion_rate - partially_invoiced_amount) * -1) ELSE (adjusted_subtotal - credits_applied) * conversion_rate - partially_invoiced_amount ) AS amount_complex , SUM(CASE WHEN (is_revert = TRUE) THEN (amount * -1) ELSE amount ) AS amount_simple -- Step 6: any final rounding occurs , SUM(CASE WHEN (is_revert = TRUE) THEN (rounded_amount * -1) ELSE rounded_amount ) AS rounded_amount FROM daily_line_items GROUP BY 1 ORDER BY 1 ASC ``` ### Credit drawdown To understand credit utilization and the revenue recognized from prepaid credits, leverage the `credit_ledger_entry` resources. | Resource type | Task | Steps | | --------------------- | ------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | | `credit_ledger_entry` | Filter to relevant ledger entries | Filter out reverted entries with `reverted_at` | | `credit_ledger_entry` | Convert entries to revenue amounts | Use the `entry_type` to determine the revenue amount and multiply by the `cost_basis` from the credit block | | `credit_ledger_entry` | Sum the revenue amounts based on their cost basis | Use the value calculated above | Alternatively, leverage `credit_ledger_entry_event` for a simpler approach—it includes the pre-calculated `recognized_revenue_amount` field and respects [accounting period locks](/revenue-reporting/revenue-recognition#actions-in-the-past-and-accounting-period-locks). | Resource type | Task | Steps | | --------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------------------- | | `credit_ledger_entry_event` | Sum the recognized revenue amounts | Sum `recognized_revenue_amount`, treating entries with `is_revert = False` as positive and `is_revert = True` as negative | The `credit_ledger_entry_event` resource includes a `recognized_revenue_amount` field that pre-calculates the revenue impact of each credit operation based on the credit block's cost basis. This is simpler than manually calculating `amount * cost_basis` from the base resource. ```sql Calculate revenue contribution from credit ledger entries {4-11,14,18} [expandable] theme={null} WITH revenue_amounts AS ( SELECT id , created_at , CAST(credit_block->>'per_unit_cost_basis' AS DECIMAL) AS cost_basis , CASE WHEN entry_type = 'amendment' THEN CAST(amount AS DECIMAL) WHEN entry_type = 'decrement' THEN CAST(amount AS DECIMAL) WHEN entry_type = 'credit_block_expiry' THEN CAST(amount AS DECIMAL) -- Everything else will recognize to 0 ELSE 0 END AS entry_amount FROM credit_ledger_entry WHERE reverted_at IS NULL ) SELECT DATE_TRUNC('day', created_at AT TIME ZONE 'UTC') AS day , SUM(entry_amount * cost_basis) AS total_revenue FROM revenue_amounts GROUP BY 1 ORDER BY 1 ASC ``` ```sql Calculate revenue contribution using credit_ledger_entry_event {4-11,17-20} [expandable] theme={null} WITH revenue_amounts AS ( SELECT id , lock_adjusted_timestamp , is_revert , CASE WHEN entry_type = 'amendment' THEN recognized_revenue_amount WHEN entry_type = 'decrement' THEN recognized_revenue_amount WHEN entry_type = 'credit_block_expiry' THEN recognized_revenue_amount -- Everything else will recognize to 0 ELSE 0 END AS entry_recognized_amount FROM credit_ledger_entry_event ) SELECT DATE_TRUNC('day', lock_adjusted_timestamp AT TIME ZONE 'UTC') AS day , SUM(CASE WHEN (is_revert = TRUE) THEN (entry_recognized_amount * -1) ELSE entry_recognized_amount ) AS total_revenue FROM revenue_amounts GROUP BY 1 ORDER BY 1 ASC ``` ### Deferred revenue Deferred revenue represents amounts billed but not yet recognized—revenue you've invoiced but haven't yet earned. To calculate deferred revenue: sum billings, less revenue recognized for a particular period. ### Unbilled revenue Unbilled revenue (also called "accrued revenue") represents revenue recognized but not yet invoiced—typically usage charges accruing on draft invoices. Unbilled revenue appears when revenue is recognized before billing occurs. This is common with usage-based charges billed in-arrears, where usage accrues throughout the billing period but the invoice isn't issued until after the period ends. ## Reconciliation reports To reconcile bank account balances, `invoice_metadata` provides details for invoices with paid status that can be combined with feeds from payment processors like Stripe to perform an account level reconciliation. If you use Orb to power your payments, the relevant Orb identifiers are included in the resource for joining against Stripe’s data. For Stripe, the external identifiers on an item represent the Stripe Product ID. For other systems like Quickbooks or Netsuite, these identifiers maps to the Item ID in those systems. ```sql Fetch Orb invoice identifiers for reconciliation [expandable] theme={null} SELECT id , invoice_number , customer_id , issued_at , due_date , paid_at FROM invoice_metadata WHERE status = 'paid' AND deleted_at IS NULL AND voided_at IS NULL ``` Orb's `item` resource also provides access to third party item identifiers to help with reconciliation. ```sql Fetching item mappings theme={null} SELECT id , external_connections AS third_party_mappings FROM item WHERE archived_at IS NULL ``` ## Contra revenue reports Invoice amounts are not always equal to the list price of the products being sold. For example, customers may be given discounts for special promotions, credit notes may be issued due to service outages, or customers may have balances that are applied against invoices from previous overpayment. The following resource types can help you understand the cases where the invoice list price may not be equal to the final amount paid by the customer. | Resource type | Description | | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `coupon` and `coupon_redemption` | Contains the coupons you’ve created with Orb and the associated Adjustment created when the coupon was redeemed. | | `adjustment` and `adjustment_interval` | Adjustments are Orb modeling for discounts (amount, percent, or usage) and minimums or maximums. They are most useful for understanding the impetus for a given adjustment on an invoice.

Adjustment Intervals are used to understand the period of time over which an Adjustment is relevant. | | `credit_note` and `credit_note_line_item` | Credit Notes are the credits given to a customer’s invoices after the invoice has been issued. Since an issued invoice is an immutable financial document, Credit Notes all for changing of the final amount owed on the invoice by creating a new financial document, the Credit Note.

Credit Note Line Items are the individual amounts applied to each invoice line item when a Credit Note is created and are useful to understand how each invoice line item’s amount is affected to the end customer. | | `customer_balance_transaction` | Customer Balance Transactions are used to understand the amount(s) applied to an invoice from the customer’s outstanding balance. Customer Balance Transactions occur when there is an outstanding customer balance that is owed to the customer. | ### Credit notes To understand the amount of money that was credited on invoices we can leverage the `credit_note` resource. Credit notes contain the reason, amount, and relevant invoice where they were applied. Since credit notes are financial documents applied on top of issued invoices, credit notes are always relevant to issued invoices. | Resource type | Task | Steps | | --------------------------- | ------------------------------- | ----------------------------------------------------------------------- | | `credit_note` and `invoice` | Filter to relevant credit notes | Filter out voided and deleted resources | | `credit_note` | Sum credit note amounts | Sum the credit note `total` and aggregate the list of issuance `reason` | ```sql Breakdown issued credit notes {4-5,10-13}[expandable] theme={null} SELECT invoice.id , invoice.invoice_number , COUNT(*) AS num_credit_notes , SUM(CAST(credit_note.total AS DECIMAL)) AS total , STRING_AGG(DISTINCT(credit_note.reason), ', ') AS reasons FROM credit_note JOIN invoice_metadata AS invoice ON invoice.id = credit_note.invoice_id WHERE credit_note.voided_at IS NULL AND credit_note.deleted_at IS NULL AND invoice.voided_at IS NULL AND invoice.deleted_at IS NULL GROUP BY 1, 2 ``` If you want to review how these amounts break down by individual line items, you can join accordingly. ```sql Breakdown credit note amounts by invoice line item [expandable] theme={null} SELECT invoice.id , invoice.invoice_number , credit_note.credit_note_number , cnli.id , SUM(CAST(cnli.amount AS DECIMAL)) AS amount , STRING_AGG(DISTINCT(credit_note.reason), ', ') AS reasons FROM credit_note JOIN credit_note_line_item AS cnli ON cnli.credit_note_id = credit_note.id JOIN invoice_metadata AS invoice ON invoice.id = credit_note.invoice_id WHERE credit_note.voided_at IS NULL AND credit_note.deleted_at IS NULL AND cnli.voided_at IS NULL AND cnli.deleted_at IS NULL AND invoice.voided_at IS NULL AND invoice.deleted_at IS NULL GROUP BY 1, 2, 3, 4 ``` ### Customer balance transactions In addition to Credit Notes, invoices may also see their issued amount get decreased by any outstanding (and positive) customer balance. Customer balances can be thought of like a payment method and are used before charging the customer since they are owed balances to the customer. When using this balance, Orb tracks a Customer Balance Transaction to associate the amount applied to the relevant invoice. In rare cases, a customer may have a negative balance (they owe money from previous invoices). When this negative balance is applied to a new invoice, it increases the amount due. We can visualize the amounts applied and their relevant invoices like so. ```sql Breakdown customer balance transactions by invoice [expandable] theme={null} SELECT invoice.id , invoice.invoice_number , cbt.created_at , cbt.type , cbt.amount -- Positive = credit applied (reduced invoice), Negative = negative balance applied (increased invoice) , CASE WHEN cbt.type = 'decrement' THEN cbt.amount WHEN cbt.type = 'increment' THEN -cbt.amount END AS effect_on_invoice FROM customer_balance_transaction AS cbt JOIN invoice_metadata AS invoice ON invoice.id = cbt.invoice_id WHERE cbt.action = 'applied_to_invoice' AND cbt.deleted_at IS NULL ``` ### Discounts and adjustments The Adjustment and Adjustment Interval resources exist to keep track of discounts, minimums, and maximum applied to invoice line items. These could be for reduced pricing, promotions / sales, and more. If we want to look into the impact of a specific Adjustment or type of Adjustment, we can leverage the Adjustment table in conjunction with the Invoice Line Item Billing table to look at all line items that have been billed. For our use case, let’s look for a specific Adjustment and see its impact on our original list price. ```sql Understanding adjustment contributions to issued invoices {4} [expandable] theme={null} WITH relevant_adjustments AS ( SELECT id FROM adjustment WHERE '' ) , line_items AS ( SELECT id , jsonb_array_elements(adjustments) AS element FROM invoice_line_item_billing ) , enhanced_line_items AS ( SELECT * , element->>'id' AS adjustment_id , element->>'type' AS adjustment_type , CAST(element->>'amount' AS DECIMAL) AS adjustment_amount FROM line_items ) , relevant_line_items AS ( SELECT * FROM enhanced_line_items AS line_items JOIN invoice_line_item_billing ON invoice_line_item_billing.id = line_items.id JOIN relevant_adjustments AS adjustments ON adjustments.id = line_items.adjustment_id ) SELECT item_id , adjustment_type , SUM(adjustment_amount) , COUNT(*) AS num_billed_line_items FROM relevant_line_items GROUP BY 1, 2 ``` ### Coupons Coupons also exist within Orb to power discounts. The Coupon table tracks the associated metadata about the coupon (which may be applied to various subscriptions) and the Coupon Redemption table tracks each time the coupon is redeemed. We can run a similar analysis to the above to understand the coupons we care about. Starting from our adjustment of interest (or coupon of interest), we can get to the same set of discount amounts. ```sql Linking coupons and adjustments {10-12} [expandable] theme={null} WITH relevant_adjustments AS( SELECT id FROM adjustment WHERE '' ) , relevant_coupons AS ( SELECT * FROM relevant_adjustments AS adjustment JOIN coupon_redemption ON coupon_redemption.adjustment_id = adjustment.id JOIN coupon ON coupon.id = coupon_redemption.coupon_id ) SELECT * FROM relevant_coupons ``` ### Subscriptions Subscriptions track the lifecycle of your end customers as they move from plan to plan. Orb allows for tracking a timeline of plans on a given subscription using the data exports. This timeline can be used to join against your data to understand which plan was active at a given time using `AS OF` joins. Note that `start_time_inclusive` and `end_time_exclusive` are the time filters that should be used to determine which time frame is relevant. ```sql Creating a subscription plan timeline [expandable] theme={null} WITH subscription_timeline AS ( SELECT customer_id , subscription_id , JSON_VALUE(plan, '$.id') AS plan_id -- Keep track of when the given plan was active on the subscription. -- N.b. start time is _inclusive_ whereas end time is _exclusive_ , created_at AS start_time_inclusive , LEAD(created_at) OVER (PARTITION BY customer_id, subscription_id ORDER BY created_at ASC) AS end_time_exclusive , ROW_NUMBER() OVER (PARTITION BY customer_id, subscription_id ORDER BY created_at ASC) AS sequence_number FROM subscription_version ) SELECT * FROM subscription_timeline ORDER BY customer_id ASC, subscription_id ASC, start_time_inclusive ASC ``` ## Data quality tips When querying Orb data exports, keep these common patterns in mind: ### Always filter reverted and voided entries Orb uses soft deletes and reverts for audit compliance. Entries are never physically deleted—they're marked with a timestamp. Include the appropriate filters based on the resource: | Resource | Filter fields | | ------------------------------ | ------------------------------------------ | | `daily_line_item_revenue` | `reverted_at IS NULL` | | `credit_ledger_entry` | `reverted_at IS NULL` | | `invoice_metadata` | `voided_at IS NULL AND deleted_at IS NULL` | | `invoice_line_item_billing` | `voided_at IS NULL` | | `credit_note` | `voided_at IS NULL AND deleted_at IS NULL` | | `credit_note_line_item` | `voided_at IS NULL AND deleted_at IS NULL` | | `customer_balance_transaction` | `deleted_at IS NULL` | For event tables (`_event` suffix), use the `is_revert` flag instead: ```sql theme={null} SUM(CASE WHEN is_revert THEN -amount ELSE amount END) ``` ### Handle nullable numeric fields Many revenue fields are nullable due to historical data or backfills. Always use `COALESCE` when aggregating: ```sql theme={null} SUM(COALESCE(rounded_amount, 0)) SUM(COALESCE(credits_applied, 0)) ``` ### Distinguish timestamp fields Revenue data includes multiple timestamp fields with different meanings: | Field | Meaning | | --------------------------------------------- | -------------------------------------------------------------------- | | `timeframe_start` / `timeframe_end` | The period this revenue covers (daily for `daily_line_item_revenue`) | | `line_item_start_date` / `line_item_end_date` | The full invoice line item period | | `invoice_date` | When the invoice was issued | | `created_at` / `timestamp` | When the record was created | | `lock_adjusted_timestamp` | Timestamp adjusted for accounting period locks (event tables only) | Use `timeframe_start` for revenue recognition timing and `invoice_date` for billing timing. ### Partial invoices require special handling When threshold billing creates partial invoices, the same line item may appear multiple times. Filter or aggregate carefully: ```sql theme={null} -- Exclude partial invoices to avoid double-counting WHERE is_partial_invoice = FALSE -- Or sum only the incremental amounts SELECT SUM(rounded_amount - COALESCE(partially_invoiced_amount, 0)) ``` ### Join tables on the right keys Common join patterns between resources: | From | To | Join key | | ------------------------- | --------------------------- | -------------------------------- | | `daily_line_item_revenue` | `invoice_metadata` | `invoice_id` | | `daily_line_item_revenue` | `invoice_line_item_billing` | `invoice_line_item_id` | | `credit_ledger_entry` | `customer` | `customer_id` | | `credit_note_line_item` | `invoice_metadata` | `invoice_id` (via `credit_note`) | | `subscription_version` | `customer` | `customer_id` | # Enterprise billing with Orb Source: https://docs.withorb.com/enterprise/enterprise-billing Enterprise customers require sophisticated billing capabilities that go beyond standardized self-serve offerings. This comprehensive guide demonstrates how to leverage Orb's enterprise features to handle complex contract negotiations, custom pricing structures, and the operational realities of enterprise sales cycles. Unlike self-serve customers who typically adopt standardized plans, enterprise customers often negotiate: * **Complex pricing structures** with volume discounts, tiered commitments, and custom rate cards * **Extended payment terms** (30, 60, or 90+ day net terms) with custom invoicing requirements * **Multi-phase implementations** with trial periods, ramp schedules, and milestone-based pricing * **Shared credit pools** across multiple business units, subsidiaries, or geographical regions * **Backdated contract amendments** to align with complex renewal negotiations and late signatures * **Custom reporting and analytics** for internal chargeback and budget allocation Orb's [diff-based billing architecture](/architecture/billing-architecture) uniquely enables these enterprise requirements by allowing you to make changes effective as of any past date, automatically recalculating invoices and credit consumption without manual intervention—a capability that sets Orb apart from traditional billing systems. ## Why Orb excels for enterprise billing ### Real-time backdating without manual reconciliation Unlike traditional billing systems that require complex manual processes for backdated changes, Orb's architecture automatically handles retroactive pricing changes with automatic invoice recalculation, credit consumption adjustments for historical periods, usage re-rating when contract terms change mid-period, and audit-safe amendments that preserve complete transaction history. ### Flexible contract modeling Orb supports both standardized enterprise tiers and completely custom contracts. You can use [plan templates](/product-catalog/build-catalog) with customer-specific overrides for scalable enterprise offerings, create completely custom contracts for unique arrangements, or implement hybrid approaches that combine standardized components with custom terms. ### Enterprise-scale event processing [Ingesting events via the Orb API](/events-and-metrics/event-ingestion) supports well into the thousands of events per second out of the box, which covers the needs of nearly all enterprise customers. For customers sending higher volumes—generally north of 10,000 events per second—we recommend ingesting via a [cloud storage bucket sync](/events-and-metrics/cloud-storage-integration) (S3 or GCS) rather than direct API calls as a best practice; this improves the resiliency of your integration and is a standalone ingestion option that doesn't require purchasing or enabling any additional product. For the largest enterprise customers generating massive, continuous event volumes—generally north of 500,000 events per second—Orb separately offers [**hosted rollups**](/events-and-metrics/high-throughput-ingestion), a dedicated product that can handle billions of events per day by rolling up event data as it's ingested, enabling real-time usage data processing spanning petabytes of events. Hosted rollups are particularly valuable for enterprise customers with high-frequency usage tracking (API calls, data transfers, compute operations), real-time billing requirements with sub-minute aggregation windows, and complex dimensional analysis across multiple grouping properties. Orb's largest customers use hosted rollups to sustain several million events per second in production. The rollup architecture uses intermediate cloud buckets as durable message queues, with configurable grouping properties, aggregation functions, and time windows (default 10 minutes with 30-second partial rollups). ### Enterprise-grade operational features Orb provides [customer hierarchy](/product-catalog/customer-hierarchy) for complex organizational billing, multi-currency support with automatic conversion, [advanced adjustments](/product-catalog/adjustments) with granular targeting and scheduling, and [phase-based subscriptions](/product-catalog/build-catalog#plan-phases) with manual and automatic triggers. ## Enterprise contract modeling ### Understanding your contract options Enterprise customers typically require one of three approaches, each with distinct advantages: #### Plan templates with subscription overrides **Best for:** Standardized enterprise tiers with customer-specific modifications This approach maintains plan lineage for easy upgrades and downgrades, supports trial phases and plan-based mutations, and enables standardized enterprise offerings with customization. You can replace specific prices with enterprise-negotiated rates, add custom [adjustments](/product-catalog/adjustments) for volume discounts, and set enterprise-specific terms like extended net payment periods. #### Hybrid approach with customer hierarchy **Best for:** Large enterprises with multiple business units requiring different pricing This approach allows you to create [parent-child customer relationships](/product-catalog/customer-hierarchy) where different business units can have their own pricing while rolling up to a single invoice. Engineering divisions might use compute-heavy plans while marketing divisions use analytics-focused plans, all appearing on the parent company's consolidated invoice. ### Advanced contract structuring #### Multi-year contracts with annual adjustments Enterprise contracts often span multiple years with predetermined price changes. Orb's [phase-based subscriptions](/product-catalog/build-catalog#plan-phases) allow you to model these complex arrangements with automatic transitions between pricing periods. Each phase can have its own duration, pricing structure, and discount levels. For example, you might structure a three-year deal with a 25% onboarding discount in year one, 15% standard enterprise discount in year two, and 10% renewal pricing in the final year, with 5% annual price increases built into each phase. #### Milestone-based implementations For enterprise customers with complex rollout schedules, you can create [manually triggered phases](/product-catalog/build-catalog#plan-phases) that advance based on implementation milestones rather than time. This might include a pilot phase with limited users, a department rollout phase with tiered pricing, and an enterprise-wide phase with volume pricing. Each phase can be triggered when specific implementation milestones are reached, giving you complete control over when pricing changes take effect based on actual deployment progress rather than arbitrary dates. ## Representing complex negotiated discounts Enterprise contracts often include sophisticated discount structures that go beyond simple percentage reductions. ### Product-specific enterprise rates You can apply different discount levels to different product categories using [adjustments](/product-catalog/adjustments). For example, compute services might receive a 20% discount due to high volume commitments, storage services might get a 15% standard enterprise rate, and support services might include a \$1,000 monthly credit. ### Graduated discount structures Orb's [tiered pricing models](/product-catalog/price-configuration#tiered-pricing) naturally support graduated discount structures where discounts increase with usage volume. You can structure pricing so that the first 10,000 units are at standard rates, the next 40,000 units receive a 20% discount, the next 50,000 units get a 40% discount, and everything above 100,000 units receives a 60% discount. ### Time-based promotional discounts Using [adjustment intervals](/product-catalog/adjustments#adjustment-intervals), you can schedule discounts for specific time periods without modifying the underlying subscription. This is perfect for new customer promotions, seasonal discounts, or other time-limited offers that automatically expire without manual intervention. ## Enterprise payment terms and invoicing ### Sophisticated payment terms Enterprise customers often require complex payment arrangements including extended net terms (30, 60, or 90+ days), disabled auto-collection for manual payment processing, [invoicing thresholds](/invoicing/threshold-invoicing) to batch smaller charges, and custom [invoice memos](/invoicing/invoice-display) with purchase order references and remittance instructions. ### Custom invoice presentation Orb supports [custom invoice grouping](/invoicing/structure#custom-invoice-grouping) using grouping keys from your event data. If your events contain properties like `cluster_name`, `department`, or `region`, Orb can automatically reorganize invoices to group line items by these properties rather than aggregating across all instances. For multi-currency scenarios, Orb supports pricing in [custom currencies](/product-catalog/prepurchase#custom-pricing-units) (virtual pricing units) with automatic conversion to real-world invoicing currencies. When customers exceed their prepaid credits, overage is automatically converted using configured conversion rates. ### Purchase order integration Enterprise customers often require purchase order references on invoices. Orb's [custom invoice memos](/invoicing/invoice-display) can include purchase order numbers, contract references, department codes, and other required information that appears on both the invoice PDF and in the [invoice portal](/invoicing/invoice-portal). ## Enterprise prepaid credits and commitment management ### Multiple custom pricing units Orb supports [multiple custom pricing units](/product-catalog/prepurchase#custom-pricing-units), each with its own credit ledger. This allows you to separate credits for different services—compute credits, storage credits, API credits—each with their own conversion rates to the plan's invoicing currency for overage billing. ### Recurring true-up invoices By default, an enterprise commitment is true-up invoiced once at the end of the commitment term. For contracts that reconcile committed spend throughout the term, set `true_up_invoicing_cadence_unit` to `monthly`, `quarterly`, `semi_annual`, or `annual` when creating or editing the commitment. Orb creates standalone true-up invoices at each cadence boundary, anchored to the commitment start date in the customer's timezone. Each recurring true-up compares cumulative eligible spend through that boundary with a prorated commitment target. The target is prorated by whole months: for example, a 12-month $12,000 commitment with quarterly true-ups uses $3,000, $6,000, $9,000, and \$12,000 as the cumulative targets. The final true-up always reconciles against the full commitment target. Recurring true-up commitments must start and end at midnight in the customer's timezone, and the commitment term must span a whole number of cadence periods. For example, a quarterly true-up commitment can span 3, 6, 9, or 12 months, but not 14 months or 15 months plus a few days. ### Shared enterprise credit pools For large enterprises with multiple business units, Orb's [customer hierarchy](/product-catalog/customer-hierarchy) enables shared credit consumption across child customers. Credits purchased by the parent customer can be consumed by child customers, with all usage appearing on the parent's consolidated invoice. This simplifies procurement while providing detailed usage visibility by business unit. ## Orb's differentiated backdating architecture This is where Orb truly excels compared to traditional billing systems. Enterprise sales cycles often result in complex timing scenarios that require retroactive changes. ### Complex renewal scenarios Consider a Fortune 500 customer whose 3-year contract expired on January 1st, but renewal negotiations continued until March 15th. The new contract includes a 25% discount on all services effective January 1st, new minimum spend commitments, and additional services starting February 1st. Traditional billing systems would require manual invoice adjustments for January-March, complex credit recalculations, and lengthy reconciliation processes with high risk of errors. Orb automatically voids original January-March invoices, recalculates all usage with the 25% discount, applies new minimum commitments retroactively, adjusts credit consumption for the entire period, and issues corrected invoices with a complete audit trail. ### Mid-contract pricing corrections When an enterprise customer was incorrectly charged $0.12/unit instead of the negotiated $0.08/unit for two months, Orb automatically identifies all affected invoices, recalculates usage at the correct rate, voids incorrect invoices and issues corrections, adjusts the credit ledger for overpayments, and provides a complete audit trail for compliance. ### Credit adjustments for backdated changes When pricing changes affect credit consumption retroactively, Orb automatically adjusts credit ledgers to reflect the corrected consumption amounts, maintaining accurate balances and providing detailed audit trails for all adjustments. ## Advanced enterprise operational workflows ### Enterprise approval workflows with pending changes Large enterprises often require multi-stakeholder approval processes for subscription changes, contract amendments, and pricing modifications. Orb's [**pending changes**](/essentials/pending-changes) feature enables sophisticated approval workflows by staging changes without immediately applying them. For enterprise contract modifications requiring legal, finance, and procurement approval, you can create pending changes that show the post-change subscription state as a preview, generate invoice previews for stakeholder review, prevent further mutations until approved or canceled, and expire automatically after 24 hours if not acted upon. The typical workflow involves sales creating a pending change with negotiated terms, finance reviewing the invoice impact using the preview, legal approving contract modifications, procurement validating purchase order requirements, and the system applying the change once all approvals are collected. ### Contract amendments and upsells When enterprise customers expand their usage or add new services mid-contract, Orb's [price intervals](/product-catalog/editing-subscriptions#add-a-new-price-to-a-subscription) allow you to model these changes with precise start and end dates, ensuring accurate billing for partial periods and smooth transitions between pricing structures. ### Renewal management For enterprise renewals, you can [create new subscriptions](/product-catalog/creating-subscriptions) or [schedule plan changes](/product-catalog/subscription-plan-change) well in advance, ensuring seamless transitions between contract periods without billing gaps or overlaps. ## Enterprise customer visibility and reporting ### Real-time usage dashboards Orb's [upcoming invoice API](/api-reference/invoice/fetch-upcoming-invoice) provides enterprise customers with visibility into their current billing period usage and costs, credits consumed and remaining balances, and line item breakdowns by service. This enables real-time spend tracking and budget management. ### Historical invoice analysis The [list invoices endpoint](/api-reference/invoice/list-invoices) enables enterprise customers to analyze historical spending patterns, track usage trends over time, and generate detailed reports for internal cost allocation and budget planning. ### Credit balance monitoring For enterprise customers with prepaid credits, Orb's [credit balance APIs](/api-reference/credit/fetch-customer-credit-balance) provide real-time visibility into active credit blocks and remaining balances, essential for enterprise budget tracking and spend management. ## Compliance and enterprise governance ### Immutable audit trails Orb maintains immutable records of all billing changes with timestamps and reasons for all pricing modifications, complete credit ledger transaction history, and preserved original documents alongside corrections for invoice amendments. ### Revenue recognition support Orb's [accounting period locks](/revenue-reporting/revenue-recognition#actions-in-the-past-and-accounting-period-locks) prevent changes to closed periods, ensuring compliance with revenue recognition requirements and providing the controls enterprise finance teams need for accurate reporting. ### Data exports for enterprise systems Enterprise customers often need to integrate billing data with their ERP or accounting systems. Orb's [data exports](/data-exports/introduction) provide daily usage summaries for internal cost allocation, monthly invoice details for accounting reconciliation, credit consumption reports for budget tracking, and subscription and customer data for CRM integration. ### Integration with enterprise systems Orb provides [Salesforce integration](/integrations-and-exports/salesforce) for syncing customer and subscription data, [NetSuite integration](/integrations-and-exports/netsuite) for automated invoice and payment sync, [webhook notifications](/integrations-and-exports/webhooks) for real-time billing event updates, and comprehensive API access for custom integrations with internal systems. ## Best practices for enterprise success ### Design for scale from day one Enterprise contracts grow in complexity over time. Build your pricing architecture to accommodate future service additions without requiring contract renegotiation, organizational changes like acquisitions or divestitures, geographic expansion with local currency and [tax requirements](/integrations-and-exports/tax), volume growth with appropriate tier structures, and event volume scaling from thousands to billions of events per day. For enterprise customers expecting massive usage volumes, consider your ingestion approach early in your architecture planning. [Ingesting events via the Orb API](/events-and-metrics/event-ingestion) handles most enterprise needs, supporting well into the thousands of events per second out of the box, and for higher-throughput workloads we recommend a [cloud storage bucket sync](/events-and-metrics/cloud-storage-integration) as a best practice for added resiliency—a standalone option that does not require hosted rollups. Customers with sustained, extreme-scale operations—generally north of 500,000 events per second—may still benefit from [hosted rollups](/events-and-metrics/high-throughput-ingestion), a separate product that Orb's largest customers use to sustain several million events per second with real-time aggregation. ### Leverage Orb's unique capabilities Don't constrain enterprise requirements to fit traditional billing limitations. Use custom contracts for truly unique arrangements, leverage backdating for complex renewal scenarios, implement [customer hierarchy](/product-catalog/customer-hierarchy) for organizational billing, and utilize [phase-based pricing](/product-catalog/build-catalog#plan-phases) for implementation schedules. ### Plan for operational complexity Enterprise billing requires careful operational planning. Integrate with existing systems using Orb's APIs and [webhooks](/integrations-and-exports/webhooks), set up proper monitoring for credit pools and spend thresholds, configure appropriate [alerts](/usage-alerting/overview) for balance depletion scenarios, plan for manual interventions when automated processes need oversight, and design approval workflows using [pending changes](/essentials/pending-changes) for contract modifications requiring multi-stakeholder sign-off. ### Provide enterprise-grade transparency Enterprise customers expect detailed visibility including real-time usage dashboards, historical analysis with detailed invoice breakdowns, credit consumption tracking with clear remaining balances, and audit trails for all billing changes and adjustments. ### Plan for the entire customer lifecycle Consider the full enterprise journey including onboarding complexity with multiple stakeholders and approval processes, mid-contract changes driven by business evolution and growth, renewal negotiations with changing requirements and pricing, and potential churn scenarios requiring prorated refunds and credit handling. ### Test complex scenarios thoroughly Enterprise billing complexity requires comprehensive testing of backdated contract changes with multiple pricing tiers and adjustments, credit pool exhaustion and top-up scenarios, multi-currency transactions if operating globally, and customer hierarchy changes affecting billing relationships. ### Maintain clear documentation Enterprise contracts require meticulous record-keeping. Use descriptive reasons for all [adjustments](/product-catalog/adjustments) and pricing changes, reference contract numbers in [invoice memos](/invoicing/invoice-display) and metadata, document business logic behind complex pricing structures, and maintain audit trails for all backdated changes and their justifications. By following these practices and leveraging Orb's enterprise-grade capabilities, you can handle sophisticated billing requirements while maintaining operational efficiency and customer satisfaction. Orb's unique architecture ensures that enterprise complexity doesn't compromise billing accuracy or create unsustainable manual overhead for your team. # Managing enterprise credits Source: https://docs.withorb.com/enterprise/managing-credits Enterprise customers often utilize prepaid credits to manage usage-based billing, ensuring predictable budgets and simplified procurement processes. This guide walks through implementing a comprehensive credit management system for enterprise deployments, from initial setup to ongoing operations. ## Overview Credit-based billing allows enterprise customers to pre-purchase usage at negotiated rates, consume services against their credit balance, and receive automated alerts and controls as balances deplete. This model provides budget predictability while maintaining usage-based flexibility. ### When to use credit-based billing Credit-based billing is ideal for: * **Enterprise contracts** with annual or multi-year prepayment commitments * **Consumption-based pricing,** where customers want budget caps and controls * **Simplified procurement,** avoiding monthly variable invoices * **Trial programs** providing free credits before paid conversion * **Multi-subsidiary organizations** sharing credit pools across business units ## Credit granting workflows There are two primary methods for granting credits to customers: ### 1. Recurring credit allocations (plan-based) Credits automatically granted on a recurring schedule through [credit allocations](/product-catalog/prepurchase#allocations) configured on the plan: * Credits have **zero dollar cost basis** * Useful for monthly/quarterly credit allowances * Automatically replenishes based on plan configuration * Best for predictable, recurring credit grants Add credit allocation dialog in plan settings ### 2. Manual credit increments (focus of this guide) Credits are manually granted through adjustments, typically for: * **Prepaid purchases**, where customers buy credit packages * **One-time grants** for trials, promotions, or compensation * **Variable amounts** based on negotiated contracts * **Invoiced credits** with a specific cost basis This guide primarily focuses on manual credit management workflows, as they offer maximum flexibility for enterprise scenarios. Adjust credits dialog ## Initial system configuration Before onboarding customers with credits, configure these foundational elements that will be reused across all credit-based subscriptions. ### 1. Create billing metrics Define metrics that track the usage customers will consume credits against: * **API calls** measured in requests * **Compute time** measured in minutes or hours * **Storage** measured in GB-days * **Data transfer** measured in GB * **Custom units** specific to your service Each metric should have clear unit definitions and appropriate aggregation methods (sum, max, unique count). Billable metric definition page ### 2. Configure custom currencies (pricing units) [Custom currencies](/product-catalog/prepurchase#custom-pricing-units) allow you to decouple credits from real-world currencies. Create pricing units like: * **Credits** - Generic unit for multi-service consumption * **Compute Credits** - Specialized for compute services * **API Credits** - Dedicated to API usage Configure conversion rates between credits and your invoicing currency for overage billing. New plan form with Enable custom pricing units checked ### 3. Design credit-enabled plans Create plans that consume credits rather than generating charges directly: * All usage prices are denominated in the relevant currency you want your credits to be defined in (e.g. USD, credits, etc) * For custom currencies (e.g. credits), define an overage rate of how much charge once the credits are fully depleted ### 4. Connect payment providers For automated credit top-ups and overage billing: * [Stripe integration](/integrations-and-exports/stripe) for card and ACH payments * Configure auto-collection settings appropriate for enterprise terms * Disable auto-issuance for enterprise customers to control invoice delivery ### 5. Configure internal monitoring Set up [webhooks](/integrations-and-exports/webhooks) for credit lifecycle events: #### Critical webhooks * `customer.credit_balance_depleted` - Trigger service restrictions when credits exhausted * `customer.credit_balance_dropped` - Alert when approaching depletion thresholds Configure thresholds below auto-invoice levels to prevent alert fatigue while maintaining visibility. ## Customer onboarding workflow ### Step 1: Create the customer Requirements: * **External customer ID** from your system of record * Customer name and contact information * [Billing address](/api-reference/customer/create-customer#request.body.billing_address) for invoice generation Customer creation checklist: * External ID mapped from CRM/ERP * Billing address with valid tax information * Payment method configured (if auto-collection enabled) * Metadata tags for segmentation ### Step 2: Configure billing settings Edit customer billing configuration: * **Payment terms**: Net 30/60/90 for enterprise contracts * **Auto-collection**: Usually disabled for enterprise manual payments * **Invoice delivery**: Email addresses for automated delivery * **Tax settings**: Exemption certificates if applicable Note: If customers pay through the invoice portal with Stripe connected, Orb automatically creates and links Stripe customers. ### Step 3: Create the subscription Add the credit-enabled plan to the customer: 1. Select the enterprise plan created earlier 2. Set subscription start date 3. **Do not override prices** unless negotiating custom credit ratios The subscription establishes the credit consumption framework without granting credits directly. ### Step 4: Grant initial credits #### Option A: Trial credits (no charge) For proof-of-concept or trial periods: ``` Adjust Credits: - Amount: [trial credit quantity] - Cost basis: $0.00 - Expiry: 14-30 days from grant date - Description: "Trial credits - expires [date]" ``` #### Option B: Paid credits (invoiced) For purchased credit packages: ``` Adjust Credits: - Amount: [purchased quantity] - Per-unit cost: [negotiated rate] - Create invoice: Yes - Auto-collect: [Yes/No based on terms] - Description: "Q1 2025 credit purchase" ``` Verify the invoice subtotal matches expected charges before finalizing. ## Ongoing credit management ### Monitoring credit consumption Track credit usage through multiple channels: 1. **Real-time balance API**: [Customer balance endpoint](/api-reference/credit/fetch-customer-credit-balance) 2. **Usage dashboard**: [Upcoming invoice API](/api-reference/invoice/fetch-upcoming-invoice) shows current period consumption 3. **Webhook events**: Real-time notifications for balance changes 4. **Reports**: Export credit ledger for historical analysis ### Handling credit depletion When credits approach depletion: 1. **Automated alerts** trigger at configured thresholds 2. **Customer notifications** via email or API webhook to customer systems 3. **Top-up workflows** for additional credit purchases 4. **Service controls** optionally restrict access when fully depleted ### Credit top-up patterns #### Manual top-ups Customer-initiated purchases through: * Self-service portal with payment method on file * Sales-assisted process for negotiated packages * Procurement-approved purchase orders #### Automated replenishment Configure automatic top-ups when balance drops below threshold: * Fixed credit amounts at predetermined prices * Requires payment method and auto-collection enabled * Useful for preventing service interruptions Set up credit top-ups dialog #### Scheduled purchases Quarterly or annual credit allocations: * Align with enterprise budget cycles * Bulk discounts for larger commitments * Predictable procurement processes Credit purchase invoice and payment schedule options ## Advanced credit scenarios ### Expiring vs non-expiring credits **Expiring credits** for: * Trial programs (14-30 day expiration) * Promotional grants (quarterly expiration) * Use-it-or-lose-it annual allocations **Non-expiring credits** for: * Prepaid enterprise commitments * Long-term contracts * Rollover agreements ### Multiple credit types Separate credit currencies for different services: ``` Compute Credits: For CPU/GPU usage Storage Credits: For data storage API Credits: For API calls Support Credits: For premium support hours ``` Each with independent: * Ledgers and balances * Consumption rates * Expiration policies * Top-up workflows ## Invoice and payment workflows ### Credit purchase invoicing When customers purchase credits: 1. **Line item generation** showing credit quantity and unit price 2. **Payment terms** following enterprise agreement 3. **Auto-collection** if configured, manual otherwise ### Overage handling When usage exceeds credit balance: 1. **Automatic conversion** from credits to currency at configured rates 2. **Overage invoice** generated at period end 3. **Payment collection** per standard terms 4. **Optional restrictions** until overage paid ## Automation and integrations ### Webhook automation Implement these webhook handlers: ```javascript theme={null} // Balance depletion handler webhook.on("customer.credit_balance_depleted", (event) => { // Restrict API access // Notify customer success team // Trigger top-up workflow }); // Low balance warning webhook.on("customer.credit_balance_dropped", (event) => { if (event.balance_amount < 20) { // Send warning email // Create support ticket // Offer top-up options } }); ``` ### Analytics and reporting Export credit data for analysis: * Consumption patterns by customer segment * Credit lifetime and expiration rates * Top-up frequency and amounts * Revenue recognition from credit purchases ## Troubleshooting common issues ### Credits not applying to usage Verify: * Subscription using credit-denominated plan * Credits granted to correct customer * Credit currency matches plan prices * Credits not expired ### Unexpected credit consumption rates Check: * Price configuration on plans * Credit-to-currency conversion rates * Adjustment rules affecting consumption * Multiple services are consuming same pool ### Invoice discrepancies Review: * Credit purchase amounts and rates * Overage conversion calculations * Timing of credit grants vs usage * Backdated adjustments affecting balance By following this comprehensive guide, you can implement a robust credit management system that scales from initial trials to large enterprise deployments, providing the flexibility and control that sophisticated customers require while maintaining operational efficiency. # Audit logs Source: https://docs.withorb.com/essentials/audit-logs This feature is available in **private preview** to all Orb customers on our **Enterprise** Plan. Contact Orb to learn about access or upgrade options. Audit logs provide a comprehensive record of actions taken within your Orb account, helping you maintain security compliance, investigate incidents, and meet regulatory requirements. ## What audit logs capture Each audit log entry records detailed information about an action performed in Orb: | Field | Description | | :-------------- | :-------------------------------------------------------------------------------------- | | **Action** | The specific operation performed (e.g., `customer.created`, `subscription.cancelled`) | | **Occurred at** | Timestamp of when the action took place | | **Actor** | Who performed the action, including their type (e.g., `api_key`, `user`) and identifier | | **Targets** | The resources affected by the action (e.g., customer ID, subscription ID) | | **Context** | Request metadata including IP address and user agent | | **Environment** | Whether the action occurred in live mode or test mode | ### Tracked actions Audit logs are generated for: * **Authentication events**: User logins and provisioning events * **Write operations**: Create, update, and delete actions across core Orb resources including customers, subscriptions, invoices, plans, and metrics ### Sample audit log event ```json theme={null} { "action": "subscription.cancelled", "occurred_at": "2025-01-15T14:30:00Z", "actor": { "type": "api_key", "id": "api_key_12345", "name": "Production API Key" }, "targets": [ { "type": "subscription", "id": "sub_abc123" } ], "context": { "location": "192.0.2.1", "user_agent": "orb-python/1.0.0" }, "metadata": { "account_id": "acct_xyz789", "environment": "live" } } ``` ## Data privacy Audit logs capture metadata about actions, not the content of those actions. Specifically, API request and response bodies are **not** included in audit log entries. This ensures sensitive customer data remains protected while still providing a complete audit trail of operations. ## SIEM integration Audit logs can be streamed in real-time to your Security Information and Event Management (SIEM) provider of choice. This allows you to: * Centralize security monitoring across all your systems * Set up alerts for suspicious activity * Correlate Orb events with other security data * Meet compliance requirements for log aggregation Contact your Orb representative to configure streaming to your SIEM provider. ## Retention Audit log retention is configurable based on your compliance and operational needs. Work with your Orb representative to set up a retention policy that meets your requirements. # Authentication Source: https://docs.withorb.com/essentials/authentication Orb's system supports a handful of login and authentication methods to help ensure the security of your Orb account. These methods include: * Password login * [OAuth login with Google or Microsoft](#google-and-microsoft-login) * [SAML SSO login (included only on certain plans)](#saml-sso) Account Admins are able to make changes to the authentication configuration by visiting the "Security" tab under "Settings." Overview Multiple login methods may be enabled at one time. This can be useful for cases like system migrations, multiple auth systems, and more. ### Inviting users Account Admins can invite new users to join your Orb account by visiting the "Users" tab under "Settings." In order to login to Orb, users are required to have been invited by an Account Admin. If an invited user has not accepted their invite, Account Admins can resend the invitation email from that user's actions menu. Add user #### User roles Orb supports three user roles, "Admin", "Editor", and "Viewer": | Role | Description | Key restrictions | | :--------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------- | | **Admin** | Full control, including managing user access and permissions, setting up integrations and exports, and viewing audit logs. | None | | **Editor** | Create, update, and manage core billing objects (plans, subscriptions, metrics, etc.); access simulation and reporting tools; monitor and re-sync integrations (Admins retain setup control). | Set up account-wide settings, integrations, or exports; lock/unlock accounting periods; manage users. | | **Viewer** | Read-only access to all core billing objects, simulation and reporting tools, and settings. | Create, edit, or delete anything; manage users. | ### Changing allowed authentication methods By default, your Orb account will start with password and Google-based login enabled. You can turn on and off all available login methods within the "Security" page. This can be done by clicking the button or dropdown on the associated login method. Dropdown Set the login method to required if you want all users to login with that method. This option is available on the login method's dropdown. ### Google and Microsoft login You can use your existing Google or Microsoft account to login to Orb. Login domains can be restricted to only allow login from those specified domains. By default, specifying no domain allows for any domain to login. Note that users will still need to have been invited ([see above](#inviting-users)) to login. Restrict domains ### SAML SSO Login via SAML SSO is not supported by default on all plans. Please contact your Orb representative if you have any questions. Orb supports logging in via SAML SSO. Connections can be created, edited, and deleted all within the "Security" page by an account admin. Overview with SSO To create a connection, use the "+ Add SSO Connection" button. Create SSO connection This connection will be added to the existing connection list in a disabled state. Hitting "Configure" will present you with the following screen to setup your connection. Edit SSO connection You can also use the "Edit" button on an existing SSO connection to edit its values. SAML SSO connections requires a couple of values from the attribute mapping. These values are: 1. Email 2. Name in either of the following forms: * Full name * First name and last name ### Login flow You can login to your Orb app by visiting our [login page](https://app.withorb.com). The flow to login is only a few, simple steps that ensures the security of the Orb product. You'll first be prompted to enter your email before continuing. Orb uses your email to determine which organizations to select for login. Email entry If there are multiple Orb organizations associated with your email, you'll have to select the relevant organization you wish to login to. If there is only one organization associated with your email, you will not see this page. Multiple Orb organizations are not included with all Orb billing plans by default. Please reach out to your Orb representative if you have any questions. Organization select Afterwards, you'll be prompted to login with your login method of choice. If your organization has been configured with only one login method and that method is SAML SSO or OAuth, you'll be directly routed to the applicable login page. Login screen ## Switching organizations As mentioned above, some customers may have access to multiple organizations. When this is the case, Orb's portal allows for easy switching between organizations by accessing the information menu in the bottom left of the Orb UI. Organization switcher # Dry run requests Source: https://docs.withorb.com/essentials/dry-run Orb's API supports the dry running of requests. Dry running API requests allows you to validate the correctness of a request without making any actual changes to the system. This feature is particularly useful for ensuring that the request does not contain any errors and for previewing the potential response before committing it. For example, when performing a dry run on a request to the price intervals endpoint to add a new add-on price to a subscription, the response will contain the modified subscription object as if the price was successfully added. However, no changes will be made to the actual subscription. If you subsequently fetch the subscription, you will observe that the price has not been applied. ## Header semantics To execute a dry run request, you must explicitly pass the following header in your API request: | Key | Value | | --------- | ------ | | `Dry-Run` | `True` | This header instructs the API to treat the request as a dry run, meaning it will process the request as if it were real but will not persist any changes. When using dry run, you cannot pass an idempotency key. If you include an idempotency key in a dry run request, Orb will return a validation error. ## Example ```json theme={null} curl --request POST \ --url https://api.withorb.com/v1/subscriptions \ --header 'Dry-Run: True' \ --header 'Accept: application/json' \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' ``` ## Unsupported endpoints While Orb's API broadly supports dry runs, there are specific event based endpoints where dry running is not supported. These endpoints are: * [`POST /v1/ingest`](/api-reference/event/ingest-events) * [`PUT /v1/events/`](/api-reference/event/amend-event) * [`PUT /v1/events//deprecate`](/api-reference/event/deprecate-event) ## Response headers When a request is sent with the `Dry-Run` header and the request is successfully processed as a dry run, the response will include the following header and value: | Key | Value | | ------------- | ------ | | `Orb-Dry-Run` | `true` | This header in the response confirms that the request was treated as a dry run and that no changes were made to the data. However, if the `Dry-Run` header is included in the request but, for any reason, the request was not processed as a dry run (for instance, if the request is sent to an unsupported endpoint), the `Orb-Dry-Run` header will not be present in the response. This absence indicates that the request was executed normally and not as a dry run. # Pending changes Source: https://docs.withorb.com/essentials/pending-changes Orb’s **Pending Changes** feature lets you stage a subscription change—like creating a new subscription or editing an existing one—*without applying it immediately*. This gives you full control over when the change takes effect, usually after payment is collected or other validation is complete. This makes it possible to persist subscription changes in Orb *without applying them*, giving you access to the full power of Orb’s billing model—including invoice generation and change tracking—even before you commit to a subscription. That means you can use pending changes to power your application’s billing UI or confirmation flows in out-of-band workflows, such as signup experiences or manual payment collection flows. It’s especially useful for powering checkout flows, where you want to preview charges, collect payment, and only then commit to the subscription or mutation. You'd use a pending change instead of creating a subscription directly when: * You want to treat the subscription as *unentitled* until payment is confirmed. * You need to control the *first* payment flow yourself, especially if you don't want Orb to immediately issue a fixed fee invoice. * You want to show your user an invoice preview *before* creating the subscription — which makes this ideal for powering checkout flows. * These benefits apply both to creating new subscriptions and mutating existing ones. ## Example use case: Checkout flow pending-change 1. Backend calls `POST /subscriptions` with header `Create-Pending-Subscription-Change: true` 2. Orb returns the subscription preview and a `pending_subscription_change` ID 3. Backend uses the invoice preview to create a PaymentIntent with Stripe 4. Frontend collects + confirms payment 5. Backend calls `/subscription_changes/{id}/apply` once payment is successful, usually providing the `previously_collected_amount` to cover the initial invoice. ## Usage To stage a pending change, pass this header on any subscription mutation endpoint: ``` Create-Pending-Subscription-Change: true ``` This applies to the following subscription mutations: * [Create subscription](/api-reference/subscription/create-subscription) * [Schedule plan change](/api-reference/subscription/schedule-plan-change) * [Trigger phase](/api-reference/subscription/trigger-phase) * [Update price quantity](/api-reference/subscription/update-price-quantity) * [Update trial for subscription](/api-reference/subscription/update-trial-for-subscription) * [Add or edit price intervals](/api-reference/price-interval/add-or-edit-price-intervals) — particularly useful for powering checkout flows with add-ons The mutation will: * Return a `pending_subscription_change` in the subscription response * Show the **post-change state** of the subscription (acts as a preview) * Prevent further changes until this change is applied or canceled You can only have one pending change at a time per subscription. ## Follow-up actions Once you’ve staged a change, use the following endpoints to manage it: ### Fetch the pending change ```http theme={null} GET /subscription_changes/{subscription_change_id} ``` Returns full metadata, including the intended post-change subscription and customer info. This can be used to: * View what will be changed * Extract associated invoice amounts * Display checkout UI to your user ### Apply the change You should explicitly pass either `mark_as_paid` or `previously_collected_amount` when applying a change if payment has been collected for the initial invoice(s) to ensure that the payment is not collected twice. ```http theme={null} POST /subscription_changes/{subscription_change_id}/apply ``` This endpoint applies the change. If an invoice was created with in-advance charges, it will be issued immediately. **Payment Parameters** The apply endpoint supports two optional payment-related parameters that can be used independently or together: `previously_collected_amount` - Amount already collected externally (e.g., via Stripe, manual payment) * This amount will be credited to the customer's balance * Helps avoid double-charging for amounts already collected in cases where mark\_as\_paid is not used `mark_as_paid` - Boolean flag to mark all *payable* invoices as finalized and paid * Use when you've collected payment externally and want to mark invoices as paid * Only affects invoices where `is_payable_now` is true in the changed resources * Does not modify customer balance when used alone **Payment Scenarios** **Scenario 1**: External payment collection without balance adjustment ```json theme={null} { "mark_as_paid": true, "description": "Payment collected via Stripe" } ``` Use this when you've collected the exact amount due externally and want to mark invoices as paid without any balance adjustments. **Scenario 2**: External payment with balance credit ```json theme={null} { "previously_collected_amount": "2999", "description": "Stripe PaymentIntent confirmed" } ``` This lets you track what was already paid for externally, and is in terms of the customer currency. The previously\_collected\_amount will be added to the customer balance and will be reflected in the issued invoice. **Scenario 3**: External payment with difference handling ```json theme={null} { "mark_as_paid": true, "previously_collected_amount": "2500", "description": "Partial payment collected" } ``` Use this when the amount collected differs from the total invoice amount. Orb will: 1. Mark all payable invoices as finalized and paid 2. Calculate the difference: `previously_collected_amount` - total\_invoice\_amount 3. Adjust the customer balance by this difference ### Cancel the change ```http theme={null} POST /subscription_changes/{subscription_change_id}/cancel ``` Cancels the pending change. This is required if you want to mutate the subscription again or if the user drops off. ## Expiration Pending changes expire automatically after **1 day**. This prevents abandoned sessions from blocking further changes. ## Previewing charges The subscription returned when a pending change is staged reflects the *intended* state. To understand the associated invoices: * Inspect the `changed_resources` field on the subscription * Look at `created_invoices`, which include both immediate and lookahead (upcoming beyond that) invoices * Use the `is_payable_now` field on created invoices to identify which invoices need payment first. These will be invoices that are payable now with only in-advance fixed fee line items. You can use this field to determine which invoices should be paid to enable access to a subscription change (eg. plan upgrade). Resources within `changed_resources` are a preview of the invoicing changes that will be made. After applying, you can expect the shape of newly created invoices to match except the `id` field, which is transient. This can help: * Identify the invoice to pay in your integration * Validate invoice totals before confirmation ### Using `Include-Changed-Resources` with pending changes You can pass the `Include-Changed-Resources` header when creating a pending subscription change to control the changed resources included in the response. For checkout flows, the `payable_invoices` mode is recommended: ``` Include-Changed-Resources: payable_invoices ``` This returns only invoices where `is_payable_now` is true, which is typically the information you need to determine upfront charges. This mode skips voided invoices, credit notes, and expensive metric recomputation, making it faster and more efficient for powering checkout UIs. You can also pass `Include-Changed-Resources: true` to get the full set of changed resources (created and voided invoices, credit notes) for a pending change. See [Preview side effects](/essentials/previews) for more details on the `Include-Changed-Resources` header. Subscriptions with a pending change cannot be mutated again until the change is applied or canceled. ## Additional behavior notes * **Pending changes can be used on existing subscriptions**, not just new ones. This includes actions like plan changes or price updates. Just note that **you cannot perform another mutation while a pending change exists**. * If you're using pending changes to **create a new subscription**, you won't be able to fetch it by ID until the change is applied. * **Pending subscriptions are skipped in migrations**. They must be confirmed before they're considered active. * **Pending subscriptions do not appear in data exports**. This avoids polluting analytics or reporting pipelines with draft states. In Orb, you'll see these resource events for pending changes when: * A new pending change is created * A pending change expires * A pending change is applied * A pending change is canceled These events appear on the customer timeline, and are attached to either the customer or subscription depending on whether the change is for a new subscription or an existing one. ## Frequently asked questions ### Can I change the expiration time for a pending change? Not today — all pending changes expire after 24 hours by default. We may expose a configurable expiration in the future. ### Can I apply a pending change if no invoice was generated? Yes. If the change doesn’t result in a charge (e.g. only free metered prices were added), you can still apply the change without issue. ### What happens if I try to apply the same change twice? Pending changes are single-use. Once applied or canceled, they cannot be reused — you’ll receive an error if you try. ### What happens if a payment fails after I apply the change? Orb has no built-in rollback for this case. You’re expected to confirm payment externally *before* calling the apply endpoint. If the payment fails after applying, you’ll need to cancel the subscription or issue a refund manually. # Postman Source: https://docs.withorb.com/essentials/postman ## Prerequisites * An active Orb account * Access to your Orb API key [here](https://app.withorb.com/settings?tab=api_keys) * A free [Postman Account](https://identity.getpostman.com/signup) ## Setup Import the Orb collection into Postman. You can [download the Postman application](https://www.postman.com/downloads/). Orb recommends forking the collection so you can safely add your API key: [![Run in Postman](https://run.pstmn.io/button.svg)](https://god.gw.postman.com/run-collection/20577229-2cfaecbf-54a1-48ec-b012-3bb037213253?action=collection%2Ffork\&source=rip_markdown\&collection-url=entityId%3D20577229-2cfaecbf-54a1-48ec-b012-3bb037213253%26entityType%3Dcollection%26workspaceId%3D7728bdfa-2fdb-408d-91bc-bef873cb9715) ### Postman environment setup A full list of available Orb API calls is structured by resource in the left sidebar. In the left sidebar, you can click on the menu button to view the documentation for the given folder or specific API request. #### Authentication Before you generate an API Key, you'll want to enable test mode in the top left of the Orb UI. Test mode gives you a safe playground that doesn't affect your production customers or data. This collection includes a Postman "Environment" called "Test Mode". In the Orb UI, you'll want to head over to the API Keys tab in the settings page [here](https://app.withorb.com/settings?tab=api_keys) to generate an API Key. Once you have that: 1. Navigate to the environment by clicking the "Environments" tab on the left side, and clicking the "Test Mode" environment. 2. Fill in the api\_key with your API key * Variables allow you to share values within the collection. * To learn more about Postman variables, see the [documentation](https://learning.postman.com/docs/sending-requests/variables/). ## Working with the collection After setup is complete, you are ready to begin making API calls. In the Orb API Reference folder, there are subfolders for each type of API resource listed in the [Orb API Reference](/api-reference). Expand the subfolders to see the HTTP methods and API call names. # Preview side effects Source: https://docs.withorb.com/essentials/previews Every billing-related subscription mutation action in Orb is atomic and transactional. This is an extremely important property because it ensures that side-effects of your actions are all or nothing and all the relevant resources that are created are visible immediately to future callers. Depending on the action, this includes the creation (and deletion) of invoices, credit notes, credit blocks, and more. Often times, it's useful to understand what resources changed as the result of an action you took. In combination with Orb's dry run functionality, this can be even more powerful, allowing you to power flows like: * Subscription preview: When creating a new subscription, view the line items on the invoice that will be issued for a checkout flow * Upgrade preview: When executing a plan change, view the line items on invoices that will be issued, and credit notes that may be created (e.g. as the result of an in-advance fee credit). This can be useful for understanding charges on an upgrade or downgrade. * Internal assertions and testing: When executing a backdated subscription action, understand the invoices that will be voided and/or re-issued Note that the invoices returned by this endpoint are expected to include quantity and usage values that reflect the current state of those resources. ## Usage To use this functionality, pass the following header into your subscription mutation call: * Key: `Include-Changed-Resources` * Value: `true` This will now serialize an extra key on the serialized subscription called `changed_resources` with the following properties: * `created_invoices` : These are the invoices that Orb will create as the result of this mutation. Note that Orb automatically creates a few lookahead invoices, which are invoices that are expected to exist in the future, so this might include more than the first invoice. * `voided_invoices`: These are the invoices that Orb will void as the result of this mutation. * `created_credit_notes`: These are the credit note objects that Orb will create as the result of this mutation. * `voided_credit_notes`: These are the credit note objects that Orb will void as the result of this mutation. Note that you should not rely on any IDs returned by these changed resources in the case that you're using this functionality in combination with the Dry-Run: True header. In these cases, the non-dry-run mutation should create equivalent resources (modulo time drift) but with different IDs. Currently, this list of changed resources is not exhaustive. Each individual entry is expected to be complete, but there may be other side effects (e.g. the removal of a draft invoice) that isn't yet returned. ### Payable invoices only If you only need the invoices that require immediate payment, you can pass a lighter-weight header value: * Key: `Include-Changed-Resources` * Value: `payable_invoices` This returns only the `created_invoices` where `is_payable_now` is true, skipping voided invoices, credit notes, and expensive metric recomputation. The response shape is the same `changed_resources` object, but only the `created_invoices` field will be populated, and only with the payable invoices. This is particularly useful for checkout flows where you only need to know the amount to charge upfront and don't need the full set of side effects. # Production readiness review Source: https://docs.withorb.com/essentials/production-readiness-review Before deploying Orb for your production billing requirements, please ensure you have thoroughly considered the following points as you transition to the production environment: ## Event ingestion * Ensure you always report events within the grace period, with a meaningful/semantic idempotency key for de-duplication. * Ensure that you've attempted to backfill events (with and without replacement) in test mode. This is required to build your workflow in production. * Use the billable metric debugging tab to ensure that your metric queries are behaving per your expectations. Remember that Orb will automatically scope each metric to its customer and billing period. * Review your metric breakdowns using the price evaluate endpoint, which allows you to `GROUP` and `FILTER` by arbitrary SQL dimensions. ### Plan setup * Ensure that usage-based and fixed fees are set up at the expected cadences on your plans. * For fixed fees, confirm that your setting of `in advance` and `in arrears` matches how you invoice—for fees paid upfront for the following month (common for platform access or seats), pick the former. For fees like support, pick the latter. * Ensure discounts are scoped to only the prices that they should apply to. Be particularly wary of stacking discounts between prices and at the plan level. Ensure you understand how the ordering of adjustments (minimums, maximums, discounts) applies to your use case. ## Subscription management * Ensure you've tested all plan upgrade/downgrade cases with your integration. Typically, this means auditing the set of invoices that will be produced as a result. Pay attention to the `effective_time` of each transition; it's common for upgrades to be immediate and downgrades to take effect at the end of the term. * Ensure that you've tested changing fixed-fee quantities if applicable, to understand how they interact with your specific subscription setup. * If you're using add-ons, test via the subscription edits API and review the timeline to understand how changing prices impact your subscription's invoices. * If applicable, ensure that your integration handles future-scheduled subscriptions and cancellations (including backdated cancellations). * If required, set metadata on the Orb customer or subscription to map to properties of your system — sometimes, this is used for 'entitlement-lite' use cases. * If your subscription has a phase, simulate phase transitions by backdating the subscription for several billing periods and confirm the set of invoices produced is what you'd expect. * Confirm that you've set the correct timezone for your customers, which will determine their billing periods. ## Prepaid credits * If you're using prepaid credits, ensure you understand the overage behavior to properly communicate and collect on invoices once credits are exhausted. * Review Orb's documentation on how prepaid credits are consumed daily — adding credits in the middle of the month will only cover subsequent charges. * Ensure you understand the distinction between prepaid credits and invoicing balance (post-tax, effectively a payment method). * Review how Orb applies adjustments (e.g. discounts) in the presence of virtual currencies or prepaid credits. ## Payments and invoicing * Have you considered which customers you'd like to auto-collect on vs. manually invoice? * Test your tax integration, ensuring your products are correctly set up and mapped. Confirm that you see the reported transaction in your tax provider. * Confirm that invoices are set to issue if this is what you'd expect automatically. * For auto-collection, confirm that you're setting the customer's default payment method in your payment provider, as this is what Orb uses to pull in and determine the payment method. * Review Orb's dunning behavior — Orb will automatically retry payment (if auto-collection is on) on a fixed schedule and when a payment method is changed/added. * If you're syncing your invoices to a solution like Stripe Invoicing, ensure that you've checked the sync behavior of each Orb invoice. Unfortunately, due to Stripe limitations, Orb is not able to sync the full set of metadata. * Ensure you've accounted for the grace period delay in invoice issuance (typically, 12 hours for any invoice containing a usage-based fee). * If you're handling manual payments, ensure you have a workflow to mark the invoice as void or paid accordingly in Orb. This will often require an API integration. ## Alerts and monitoring * Ensure you've tested that alert thresholds are properly set up for key subscription metrics like usage and costs. * Verify that webhook endpoints handling alerts are operating correctly and can process alerts at scale. * Test automatic top-up behavior if using prepaid credits to ensure proper triggering and credit application. * Confirm that webhook error reporting and retries are properly configured. * Review alert configuration inheritance from plans to subscriptions and test override behavior. ## Data and reporting * Ensure any data exports to accounting systems have been tested for accuracy and completeness. * Verify that revenue recognition rules are correctly configured for your business model. * Test that any custom SQL metrics are performing efficiently at production scale. * Confirm proper timezone handling across different data views and exports. * Validate that credit consumption data is accurately reflected in reporting views. ## Security and compliance * Ensure API keys are properly scoped and rotated according to your security policies. * When rotating or retiring API keys, revoke old keys only after dependent services have been updated. Revocation is irreversible and immediately stops any service or integration using that key. * Verify webhook signatures are being validated on your endpoint. * Test that user permissions and access controls are properly configured. * Confirm proper handling of sensitive customer data in invoices and portal views. * Review audit log configuration to ensure proper tracking of system changes. ## Integration testing * Test all third-party integrations (payment processors, accounting software, etc.) in a staging environment. * Verify proper error handling for failed API calls to external services. * Ensure proper reconciliation between Orb and external systems for key data. * Test the complete quote-to-cash flow with all integrated systems. * Validate proper handling of currency conversions if using multiple currencies. ## Customer experience * Test the complete customer portal experience and verify branding configuration. * Ensure invoice templates and layouts are properly configured. * Verify email delivery settings and test all email templates. * Review customer-facing error messages and ensure they are helpful. * Test the experience of customer support tools for viewing and managing subscriptions. ## Operational readiness * Document procedures for common operational tasks like subscription changes and invoice adjustments. * Train customer support team on using the Orb dashboard and common troubleshooting steps. * Create playbooks for handling common billing scenarios and edge cases. * Set up monitoring for key system health metrics. * Establish escalation paths for critical billing issues. ## Backup and recovery * Verify proper handling of duplicate event ingestion during system recovery. * Test procedures for correcting incorrect usage data or billing errors. * Document process for handling retroactive price changes or credits. * Ensure proper backup of critical configuration and customer data. * Test recovery procedures for failed subscription changes or invoice operations. # SDK Source: https://docs.withorb.com/essentials/sdk Orb's SDK is available in **Python**, **TypeScript**, **Go**, **Java**, **Kotlin**, and **Ruby**. Each SDK includes built-in support for [idempotency](/api-reference/idempotency), [pagination](/api-reference/pagination), retries, and more. If we don't yet offer an SDK in your preferred language, please reach out to us at [team@withorb.com](mailto:team@withorb.com). You can also use our [OpenAPI spec](https://api.withorb.com/spec.json) to generate a client in your language of choice. The spec supports a `version` parameter with values `3.0` or `3.1` (default: `3.1`). | SDK | Installation Command / Dependency | Links | | -------------- | ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------- | | **Python** | `pip install orb-billing` | [PyPI →](https://pypi.org/project/orb-billing) | | **TypeScript** | `npm install orb-billing` | [NPM →](https://npmjs.com/package/orb-billing) | | **Go** | `go get github.com/orbcorp/orb-go` | [GitHub →](https://github.com/orbcorp/orb-go) | | **Java** | `com.withorb.api orb-java {version}` | [Maven →](https://central.sonatype.com/artifact/com.withorb.api/orb-java) | | **Kotlin** | ` com.withorb.api orb-kotlin {version}` | [Maven →](https://central.sonatype.com/artifact/com.withorb.api/orb-kotlin) | | **Ruby** | `gem install orb-billing` | [RubyGems →](https://rubygems.org/gems/orb-billing) | # Timezone localization Source: https://docs.withorb.com/essentials/timezones Orb provides different timezone configurations to best serve the needs of your business. Once you determine your handling of timezones on an account and customer basis, Orb's APIs and web UIs provide a consistent experience. ## System-wide UTC The default way to configure your Orb account is to use UTC, or no timezone offset, for your Account and for all your customers. With this configuration, billing cycles for all customer subscriptions will begin and end at a UTC midnight boundary, regardless of where the business is located. Naturally, this translates to how usage is displayed in dashboards (e.g. day buckets will be UTC aligned) as well as other timezone localized features such as credits expiry. Invoices across all your customers will be generated at a consistent time after UTC midnight, depending on your account's grace period. Note that the System-wide UTC timezone is also the easiest to reason about with respect to event reporting, which enforces that events are sent with a UTC timezone `timestamp` field. UTC billing is common in infrastructure use cases, where server (or other automated resources) operate on UTC time and emit events naturally aligned with UTC. ## Single account timezone With a single account timezone, you can align all customer subscriptions to the timezone that best matches *your* business' preferred hours. For example, a business located in the San Francisco area may set the account-wide timezone to `America/Los_Angeles` to ensure that the billing cycle for all their customers occurs on the 1st of the month PT. With this configuration, billing cycles for all customer subscriptions will have boundaries aligned to the specific timezone. This can be particularly convenient to provide the most predictable invoicing experience for your own internal teams; all invoices will be generated at a specific time in your timezone, depending on the configured grace period after the billing cycle end. ## Per-customer timezone Orb provides the flexibility to set each customer's timezone *individually*. This can be a powerful option to provide end customers with a subscription cycle that's simple for them to understand. For example, if you report an event with `timestamp` = `2022-03-01T07:00UTC` and have Orb configured with per-customer timezones, then that event will fall in a February billing cycle for a customer set to `America/Los_Angeles` but will fall in the March billing cycle for a `UTC` timezone customer. Per-customer timezones can also create a more predictable user experience for your end users. For example, in a pre-paid credits system, if you set an `expiry_date` for credits at `2022-05-02`, Orb will automatically ensure that credits expire at the start of the date *in that customer's timezone*. In this case, Orb will generate invoices an appropriate time after midnight *in the local timezone* of the customer, accounting for the grace period. Depending on where customers are located, this may mean that invoices are generated throughout your business day. # Integrate with cloud storage Source: https://docs.withorb.com/events-and-metrics/cloud-storage-integration S3 and GCS integrations are not included with all Orb billing plans by default. Please reach out to your Orb representative if you have any questions. Orb is able to automatically pull in events from an S3 or Google Cloud Storage bucket that's provisioned for the purposes of an event export. This is a particularly helpful option if you're executing a regular [`UNLOAD`](https://docs.aws.amazon.com/redshift/latest/dg/r_UNLOAD.html) from a data warehouse, which can directly send event rows to S3 in the required format. Note that the following instructions are for our S3 integration, but the GCS integration is similar. ### Event shape This S3 integration requires that events are in the shape of the [ingestion API format](/api-reference/event/ingest-events), but Orb provides key features to make this more ergonomic: * The S3 sync client supports both `.jsonl` (new line delimited JSON) as well as `csv` formats. * Any top-level fields in the event row that are not a recognized field will automatically be added to the `properties` dictionary. * Orb supports mapping fields present in the events to API field names within the sync, avoiding manual remapping in your pipeline (e.g. if your field is called `id` but should map to Orb's `idempotency_key`) #### JSONL example ```json theme={null} {"idempotency_key": "g7BerX9nQJaohBee2n4gfG", "event_name": "file_processed", "external_customer_id": "mVtTLHyd92vJprC3", "timestamp": "2023-04-03T09:56:50.902462Z", "billable_calls": 13, "api_calls": 200} {"idempotency_key": "4QiDyKyDpovp9V9Qa9QZaE", "event_name": "file_processed", "external_customer_id": "mVtTLHyd92vJprC3", "timestamp": "2023-04-03T09:56:50.902507Z", "billable_calls": 13, "api_calls": 200} {"idempotency_key": "2BUUUkoAdgVvedAkW56JkL", "event_name": "file_processed", "external_customer_id": "mVtTLHyd92vJprC3", "timestamp": "2023-04-03T09:56:50.902529Z", "billable_calls": 13, "api_calls": 200} ``` #### CSV example ```csv theme={null} idempotency_key,event_name,external_customer_id,timestamp,billable_calls,api_calls VScNAmR7n9n8g9A23eLhHk,file_processed,mVtTLHyd92vJprC3,2023-04-03T09:55:04.320106Z,13,200 LiyFeBbcGcvJvUcoYh7gEM,file_processed,mVtTLHyd92vJprC3,2023-04-03T09:55:04.320134Z,13,200 JZbZBzT93ApSrdAaJ23Hwu,file_processed,mVtTLHyd92vJprC3,2023-04-03T09:55:04.320151Z,13,200 ``` ### Suggested file write patterns In order to ensure freshness of usage data in Orb, we recommend uploading event files to the cloud storage bucket at least once an hour. Additionally, we do not enforce a size restriction on files sent to the bucket, but unexpected delays may occur when sending individual files with greater than 250,000 events. Our recommendation is to send multiple files with fewer events, ideally in batches of 2,000 to 20,000 events per file, in order for the S3 sync service to appropriately parallelize ingesting the events. ### Rate limits Events ingested via the S3 sync are subject to Orb's event volume limits as described in the [rate limits](/api-reference/rate-limits) section of our docs. To request a rate limit increase, please reach out to the Orb team. ### S3 sync setup At a high-level, the S3 sync requires the ARN of two S3 buckets: an events export bucket and a dead-letter queue bucket. You must [grant a specific Orb role access](https://repost.aws/knowledge-center/cross-account-access-s3) to your events export bucket with the following policy: ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::970006758186:role/" }, "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::" }, { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::970006758186:role/" }, "Action": [ "s3:GetObject", "s3:GetObjectAcl" ], "Resource": "arn:aws:s3:::/*" } ] } ``` Once Orb has the ARN of the events export bucket, Orb will add permissions to a provisioned SQS queue so that the S3 bucket can write to it. Orb uses [S3 Event Notifications](https://docs.aws.amazon.com/AmazonS3/latest/userguide/NotificationHowTo.html) to listen for new files added to the bucket, and requires the `s3:ObjectCreated:*` event type which can be configured via the [AWS Console](https://docs.aws.amazon.com/AmazonS3/latest/userguide/enable-event-notifications.html). Note that this SQS queue should be in the same region as the bucket, so Orb will provide a provisioned SQS ARN once the bucket region is known. ### Dead-letter queue When Orb encounters a file that cannot be parsed, or an event that contains validation errors, Orb will log any failures to the dead-letter-queue bucket to avoid blocking the rest of the event pipeline. Allow an Orb role read and write permissions for this bucket by adding a bucket policy, following the outline in this [support article](https://repost.aws/knowledge-center/cross-account-access-s3): ```json theme={null} { "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::970006758186:role/" }, "Action": "s3:ListBucket", "Resource": "arn:aws:s3:::" }, { "Effect": "Allow", "Principal": { "AWS": "arn:aws:iam::970006758186:role/" }, "Action": [ "s3:GetObject", "s3:GetObjectAcl", "s3:PutObject", "s3:PutObjectAcl" ], "Resource": [ "arn:aws:s3:::/*" ] } ] } ``` ### Customer Requirements for S3 Sync Setup To complete the S3 sync setup, securely provide the following information: #### Required information * **ARN of events export bucket:** The Amazon Resource Name (ARN) of the S3 bucket that will be used to export events. * **Region of events export bucket:** The AWS region where the events export bucket is located. * **ARN of dead-letter queue bucket:** The ARN of the S3 bucket that will be used as the dead-letter queue for failed events. * **AWS Account ID**: The AWS account ID associated with your bucket(s). * **Event shape**: The shape of the events you will be sending. #### Important notes * Our team will provide you with the IAM role that Orb will use to access the events export bucket and dead-letter-queue bucket. * Please ensure that the necessary permissions and policies are in place, as outlined in the S3 sync setup instructions, to allow Orb to access and read from the events export bucket and write to the dead-letter queue bucket. ## Frequently asked questions ### Does the folder prefix in my S3 bucket matter? The folder prefix you use in your S3 bucket does not matter for Orb's ingestion process, as long as you don't target a specific prefix pattern in the event listeners that push notifications to SQS. Orb will process files from any folder structure within your bucket. You have the flexibility to organize your event files using any folder hierarchy that suits your data pipeline needs (e.g., `events/2024/01/15/`, `raw-data/billing-events/`, or no prefix at all). The only consideration is when configuring your S3 Event Notifications - if you specify a prefix filter in your event notification configuration, make sure it aligns with where your event files will be placed in the bucket. ### Does deleting files from my S3 bucket affect data that's already been ingested? No, deleting files from your S3 bucket will not affect any data that has already been ingested into Orb. Our event notifications on your bucket are configured for "All object create events" only, which means our ingestion pipeline only listens for when new files are added to your bucket. File deletion events do not trigger any part of the ingestion process. Once Orb has processed and ingested events from a file in your S3 bucket, that data becomes part of your Orb account's event history and is independent of the original file's existence in your bucket. You can safely delete files from your bucket for storage management purposes without worrying about affecting your billing data or metrics in Orb. # Construct metrics Source: https://docs.withorb.com/events-and-metrics/construct-metrics To get started with creating your first usage metric, check out the [quickstart guide](/quickstart/metrics). ## Usage-based billing Unlike subscription billing providers which provide metered billing, usage reporting in Orb isn’t simply a single aggregate value that the system consumes periodically and multiplies with a price amount. Instead, Orb uses a fundamentally different paradigm of to support usage-based billing on events, where usage is calculated based on a query over raw events emitted into Orb. How you calculate billable usage is separate from what events are emitted by your system, giving you the flexibility to structure your event stream the way you see fit. This model guarantees **auditability** and **accuracy** down to *every usage event*. Orb transforms billing from a once a month “billing run” to a real-time process, giving you an unmatched level of flexibility and the ability to time travel and see how usage grows over time. ## Billable metrics A billable metric is defined by the query that transforms raw usage events into meaningful values for your customers. For example, the following concepts would be ideal billable metrics in Orb: 1. Transaction volume (USD) 2. Milliseconds of function compute in `us-east-1` 3. Monthly active users 4. Storage (GB-hr) Orb's billable metrics are designed to track usage, instead of the resultant amount to charge. For example, if your service costs \$3 per API call, best practices would be for your billable metric to simply aggregate API calls, and not multiply by \$3. Pricing information is better represented by the [price](/core-concepts#plan-and-price) object. Billable metrics are always evaluated on a per-customer basis. Billable metric names are internal identifiers and are not shown to customers on invoices or in the customer portal. Customers only see names configured on prices and line items. ## Composing a metric A billable metric consists of event filters as well as an aggregation clause. Event filters determine which events should affect the metric, and these filters are based on the properties of each individual event. Note that during ingestion, `properties` is a dictionary of primitive key-value pairs, and any property of the event can be used when defining your metric. Suppose we take processed transaction volume as an example of a metric, where your reported events represent each transaction along with its status and the payment method that was used: ```json theme={null} { "event_name": "transaction_processed", "external_customer_id": "fintech_inc", "properties": { "capture_status": "pending", "payment_method": "ach", "amount_cents": 4592.19, "currency": "usd" } } ``` To calculate processed transaction volume, you could configure your metric with the matching filters on `event_name`, `capture_status`, and perhaps `payment_method`. ``` event_name = `transaction_processed` AND capture_status = `success` AND (payment_method = `card` OR payment_method = `direct_debit`) ``` Your metric also needs to define how to *aggregate* the total usage from matching events. In this case, that could be a simple `SUM(amount_cents)`. As time progresses, Orb will track this aggregate value for each of your customers separately and automatically update usage dashboards, alerting, and workflows. ## Filter logic Filters determine which events contribute to your metric. Orb evaluates filters using standard boolean logic: * **AND**: All conditions must be true for an event to be included * **OR**: At least one condition must be true When combining AND and OR, use parentheses to group conditions: ``` event_name = 'transaction' AND status = 'success' AND (method = 'card' OR method = 'bank_transfer' OR method = 'ach') ``` This matches successful transactions using any of the three payment methods. ## Fully flexible metrics using SQL See the [advanced metrics](/extensibility/advanced-metrics) guide for more information on how to use SQL to create fully flexible metrics. # Customer aliases Source: https://docs.withorb.com/events-and-metrics/customer-aliases Orb provides the concept of an `external_customer_id` as an alias to Orb's own generated `customer_id` to allow ergonomic interaction with our APIs. This alias makes the integration process with Orb faster (by preventing the propagation or lookup of an extra piece of metadata) and can have significant consistency benefits as it avoids denormalization of an Orb ID in your system. Orb's APIs are designed to allow this alias *everywhere* a `customer_id` is accepted, rather than restricting its use to specific surface areas. In order to set up this association, use the [Create customer](/api-reference/customer/create-customer) endpoint and optionally pass in an existing identifier native to your system as the `external_customer_id`. Note that this identifier must be unique across all Customer resources in your Account, but can otherwise be an arbitrary string. ## Usage in Orb APIs This mapping is most useful in the [Ingest usage event](/api-reference/event/ingest-events) call, where each event can simply contain a reference to the `external_customer_id` without looking up any Orb resources. Orb also provides other convenient APIs using the `external_customer_id` field, which save you the extra lookup of mapping a native ID to an Orb ID. Any endpoint definition that acts or filters based on a `Customer` resource will have an accompanying implementation using `external_customer_id`. For example, Orb supports both [Update customer](/api-reference/customer/update-customer) using the Orb ID as well as the [Update customer by external ID](/api-reference/customer/update-customer-by-external-id) action. Similarly, the [List Subscriptions](/api-reference/subscription/list-subscriptions) endpoint can be filtered either by the `customer_id` or `external_customer_id`. Note that when both `external_customer_id` and `customer_id` fields are present as options for an endpoint, Orb expects that you pass in exactly one and not both parameters. ### URL encode External Customer IDs Since the `external_customer_id` param can be present in a request URL and it may contain special characters, you should always URL encode the value when it's sent as a path parameter or query parameter. For example, the ID `en/cus/?1500` should be sent in a URL path as `en%2Fcus%2F%3F1500`. # Ingest events Source: https://docs.withorb.com/events-and-metrics/event-ingestion Use the [Ingest Usage Events](/api-reference/event/ingest-events) endpoint to send usage events into Orb. Orb operates on the paradigm of [events-based billing](/events-and-metrics/construct-metrics), where usage is calculated over raw events data. If you're looking to get started with sending your first event to Orb, check out the [Ingestion Quickstart Guide](/quickstart/ingest). ## Events and metrics A single usage event sent to Orb typically corresponds to either a user-triggered action or an axis of measurement, depending on your business. Each event is labeled with an `event_name`, which conceptually identifies the action taken, as well as a user-defined dictionary of `properties`. Metrics are assembled by querying over events (of potentially different `event_name`), and can flexibly filter and aggregate on any property of the underlying events. The following table provides some examples of events you might send to Orb based on the nature of your business, to illustrate how broadly an event can vary. In each case, the `properties` that you send alongside these names will determine your event's full semantics. | Business Domain | Event examples | | --------------------------- | ----------------------------------------------------------------------------------- | | Financial APIs | `transaction_processed`
`payment_authorized`
`account_linked` | | Cloud & Data Infrastructure | `cluster_compute`
`offline_storage`
`network_ingress` | | Communications | `message_transmitted`
`call_processed`
`recording_uploaded` | | Developer Tooling | `async_job_run`
`container_uptime`
`pipeline_execution` | | Integrations | `source_connected`
`batch_events_synced`
`connection_health_refreshed` | | Analytics | `active_user_login`
`event_ingested`
`query_compute_job` | ## Event volume and concurrency Orb's [ingestion API](/api-reference/event/ingest-events) is designed to manage high-volume, real-time use cases. Each individual call supports sending a batch of events. Individual ingestion requests can also be sent concurrently, which is useful in environments with multiple distributed reporters sending data to Orb that don't require coordination. Orb provides per-event idempotency through the API to guarantee that duplicates are never processed within the account grace period. By default, Orb limits the request size to 500 events per batch. By default, the ingestion API is [rate limited](/api-reference/rate-limits) to 10,000 events per minute, but this is a default rather than a hard ceiling—please give our team a heads up if you plan to continuously send higher volumes, so that we may raise your limit and provision dedicated throughput capacity. The ingestion API (regardless of the integration mechanism) is designed to scale to tens of millions of events per day, and as your volume grows into the thousands of events per second, see the guide on [high throughput ingestion](/events-and-metrics/high-throughput-ingestion) for best practices, including when it's worth considering Orb's hosted rollups product for pre-aggregating extremely high-volume event streams while still providing the same idempotency and real-time guarantees. To ensure that test mode workloads do not affect live mode availability or performance, the ingestion API will be limited to 2000 events per minute and 10 requests per second for test mode events. Note that significantly higher throughput, by orders of magnitude, is available in production environments. ## Determining event schema Metadata passed in via `properties` to Orb does not have to conform to an up-front decided schema, and you can specify any number of tags or labels that might be relevant to billing. Each event should include properties required to compute an aggregate on the basis of those events. You're encouraged to send additional metadata, even if it's not immediately useful for billing in the short-term. Additional event properties may be used for future metrics you want to build, or formatting invoices (e.g. if you provide a `region` property that doesn't affect prices, your invoices may still employ it for grouping line items). Sending those properties in your initial integration will help avoid backfills and amendments. Property values should be primitives: strings, booleans, or numbers. Numeric property values must be between `-9223372036854775808` and `9223372036854775807`; events with numbers outside this range are rejected during ingestion validation. In the Financial APIs use case above, the entirety of the event might look like: ```json theme={null} { "event_name": "transaction_processed", "timestamp": "2022-02-02T00:00:00Z", "external_customer_id": "9fc80ac0-d9ff-11ec-9d64-0242ac120002", "properties": { "processing_status": "succeeded", "transaction_amount": 3513.36, "payment_method": "ach" } } ``` For an infrastructure service, on the other hand, the event might include properties that explain the compute incurred: ```json theme={null} { "event_name": "cluster_compute", "timestamp": "2022-02-02T00:00:00Z", "external_customer_id": "9fc80ac0-d9ff-11ec-9d64-0242ac120002", "properties": { "cluster_name": "staging-cluster-1", "compute_ms": 912, "aws_region": "us-east-1", "compute_tier": "async_tier_2" } } ``` ## Tracing ingested events Events themselves can be inspected in Orb via a trace view. This can be helpful for understanding how events are being processed, or in order to test your integration. The Events usage view also shows account-wide counts for preset time ranges, including events ingested, events filtered as duplicates, and events filtered due to idempotency key reuse. Use these cards to spot ingestion-volume changes or duplicate-event patterns before drilling into individual event traces. This view includes: * A full view of the event, including the `properties` that were sent with the original payload * Attribution information to the Orb Customer * Information about whether the event contributed to an active subscription, and if it led to a deduction for pre-paid plans * How the event contributed to the invoicing cycle, past or upcoming Tracing ingested events ## Integrations for event ingestion Orb supports multiple different ingestion strategies to maximize the ease and efficiency of your integration. In addition to the primary ingestion endpoint, events can also be ingested via the following integrations. | Integration | Setup required | | ----------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Segment](https://segment.com/) | Add Orb as a destination in Segment and set up event mappings to the Orb event schema. Orb automatically ingests `track` calls from Segment. | | [S3 / GCS Integration](/events-and-metrics/cloud-storage-integration) | Set up an S3 bucket; Orb will handle listening for event notifications for files added and automatically manage file-level idempotency and API retries. | | Logs Infrastructure (e.g. [Kinesis](https://aws.amazon.com/kinesis/), [Cloudwatch](https://aws.amazon.com/cloudwatch/)) | Set up a Lambda that runs within your VPC, and/or a Cloudwatch filter to send a subset of logs to Orb in a cloud storage bucket. | Please reach out to the Orb team in order to provision these connections for your account. # Hosted rollups Source: https://docs.withorb.com/events-and-metrics/high-throughput-ingestion Ingesting events directly via the [Orb API](/events-and-metrics/event-ingestion) comfortably supports well into the thousands of events per second, still allowing for brief spikes in event volume, and covers the vast majority of use cases, including infrastructure customers. Orb stores your raw event data, which gives you the most flexibility because you can query it in any way you'd like with billable metrics. Once you're sustaining more than roughly 10,000 events per second, we recommend moving to a [cloud storage bucket sync](/events-and-metrics/cloud-storage-integration) (S3 or GCS) as your integration path, rather than sending events directly to the API. Batching events into files that sync to a bucket meaningfully improves the resiliency of your integration at high volumes, and it's the approach we suggest once you're consistently operating at this scale. This is guidance rather than a hard requirement, and is a separate ingestion option from hosted rollups described below—it does not require purchasing or enabling hosted rollups. Once you're operating at a much larger scale—generally north of 500,000 events per second—and want Orb to pre-aggregate that data in real time rather than storing every raw event, Orb offers a service called "hosted rollups," rolling up your event data as it's ingested into Orb. These rollups ensure fast access to real-time usage data spanning **petabytes** of events. Orb's hosted rollups architecture has undergone sustained stress testing well beyond 500,000 events per second, and Orb's largest customers use hosted rollups to sustain several million events per second in production. If you have a specific event load you're looking to support, our team can help you set up a test environment to illustrate how Orb can support your volume. ## Architecture and configuration Orb's hosted rollups typically uses an intermediate cloud bucket as a durable message queue for events. A rollup configuration in Orb is defined by: * A set of **grouping** properties. This is a tuple of properties that identify each grouping that Orb emits per timeframe. * An **aggregation function** for each property you’d like to aggregate in a given rollup. By default, Orb will also include the count of events in the rollup. * The time window over which you’d like to compute the final rollups (default is 10 minutes). * The frequency at which you’d like Orb to emit partial rollups (default is 30 seconds). Orb emits partial frequent rollups to ensure you have access to usage data as it arrives, not just when the aggregation window is complete. Streaming aggregation ### Example: Rollups on data download Imagine you’re a file storage company that charges by the number of files downloaded and the total bytes downloaded. You offer three tiers of download speeds (fast, medium, and slow), charging more for higher speeds. Your customers download hundreds of thousands of files every second, and you send every download to Orb as a usage event with the following shape: ```json theme={null} { "id": "afdb028a-c510-11ed-9faa-0a58a9feac02", "customer": "617cb4de-c511-11ed-9faa-0a58a9feac02", "download_timestamp": "2022-08-14T15:14:31.132Z", "bytes_downloaded": 3256, "download_speed": "fast" } ``` You configure Orb’s hosted rollups with the following properties: * `id` is the field Orb will use to deduplicate events. * The `customer` property maps to `external_customer_id`, and uniquely identifies an Orb customer. * `download_timestamp` is the timestamp used to bucket events into rollup windows * Sum over `bytes_downloaded` grouping by `download_speed` * Aggregate over 10 minute windows, emitting partial rollups every 30 seconds Orb will then ingest these aggregates with `timestamp` as the start of the aggregation window: ```json theme={null} { "idempotency_key": , "timestamp": "2022-08-14T15:10:00.000Z", "external_customer_id": "617cb4de-c511-11ed-9faa-0a58a9feac02", "count": 3037816, "sum_bytes_downloaded": 25343235423, "download_speed": "fast" } ``` You’ll note that `bytes_downloaded` in the original payload translates to `sum_bytes_downloaded` in the ingested event, signifying that Orb has automatically summed this field for all events that have `download_speed: fast`, in the 10 minute timeframe `2022-08-14T15:10:00.000Z` to `2022-08-14T15:``20``:00.000Z`. Once these events are ingested, you can then define billable metrics over them, retaining the flexibility to evolve the way you charge without running a backfill. ## Querying over rollup events Since Orb's rollups feature also outputs intermediate rollups, it's important that these are accounted for in the billable metric queries that you form over them, effectively by executing a `GROUP BY` over the relevant properties and the grouping. Orb provides ergonomic syntax for querying over aggregated events as a "view", abstracting this rollup mechanism away. For example, the following query: ``` SELECT SUM(aggregated_count) FROM ( SELECT MAX(count) as agg_count FROM events WHERE event_name = 'API_REQUEST_aggregated' AND data_status = 'success' GROUP BY timestamp_millis, data_region, data_clusterId ) ``` can be written instead as: ``` SELECT SUM(aggregated_count) FROM AGGREGATED_EVENTS('API_REQUEST') WHERE data_status = 'success' ``` # Parameterized metrics Source: https://docs.withorb.com/events-and-metrics/parameterized-metrics **This feature is in private preview.** Parameterized metrics are currently available to a limited set of customers. Reach out to your Orb contact to get access. Orb's [billable metrics](/events-and-metrics/construct-metrics) let you define a query over raw usage events to calculate meaningful billing values. In many cases, you want the *same* metric logic to apply across customers, but with a value that varies per subscription — a rate multiplier, a regional coefficient, or an allocation threshold. **Parameterized billable metrics** solve this by letting you define named variables directly in your metric's SQL, and then set values for those variables on each subscription. This means you can write one metric and reuse it across your entire customer base, even when different customers have different contractual terms. ## Why parameterized metrics Without parameters, modeling customer-specific rates requires creating a separate billable metric for each variation — a "Revenue at 1.5x" metric, a "Revenue at 2.0x" metric, and so on. This approach doesn't scale: every new contract negotiation means a new metric, and the list quickly becomes unmanageable. Parameterized metrics let you define the logic once: ```sql theme={null} SELECT SUM(amount * {{rate_multiplier}}) FROM events WHERE event_name = 'transaction_processed' ``` The `{{rate_multiplier}}` placeholder is a **parameter**. When you create the metric, you give it a default value (e.g. `1.0`). When you create a subscription, you can override that value per price interval — one customer might get `1.5`, another `2.0`, and a third might use the default. Orb resolves the parameter values at billing time and substitutes them into the query, so every downstream feature — usage dashboards, alerts, invoices — works exactly as it does with standard metrics. ## Defining parameters in SQL Parameters use the `{{parameter_name}}` syntax inside a [custom SQL metric](/extensibility/advanced-metrics) definition. Parameter names must start with a letter or underscore and can contain alphanumeric characters and underscores. ```sql theme={null} SELECT SUM(compute_seconds * {{cost_per_second}}) FROM events WHERE event_name = 'job_completed' ``` You can use multiple parameters in a single metric: ```sql theme={null} SELECT SUM( CASE WHEN region = {{target_region}} THEN quantity * {{premium_rate}} ELSE quantity END ) FROM events WHERE event_name = 'api_request' ``` Parameters can appear anywhere a literal value would — in arithmetic expressions, `CASE` conditions, `WHERE` clauses, and so on. They are not limited to numeric values; string parameters work as well (e.g. for filtering by a configurable region or tier name). ## Creating a parameterized metric When you create a metric using custom SQL that contains `{{...}}` placeholders, Orb requires you to provide a **parameter definition** for each parameter. A parameter definition includes: | Field | Description | | --------------- | ---------------------------------------------------------------------------------------------- | | `name` | The parameter name, matching the `{{name}}` used in the SQL. | | `default_value` | The value to use when a subscription does not provide an override. Must be a number or string. | The type of the parameter (numeric or string) is inferred from the `default_value` you provide. If the default is a number, Orb expects numeric overrides; if the default is a string, Orb expects string overrides. ### API When creating a metric via the API, include the `parameter_definitions` field: ```json theme={null} { "name": "Revenue with rate multiplier", "sql": "SELECT SUM(amount * {{rate_multiplier}}) FROM events WHERE event_name = 'transaction_processed'", "parameter_definitions": [ { "name": "rate_multiplier", "default_value": 1.0 } ] } ``` ### Validation rules Orb validates parameter definitions against the SQL at creation time: * Every `{{parameter}}` in the SQL must have a corresponding definition. * Every definition must correspond to a `{{parameter}}` in the SQL — you cannot define unused parameters. * A metric can have at most **10** parameter definitions. * Each `default_value` must be a number or a string. If validation fails, the metric creation request returns an error describing the mismatch. ## Overriding parameters on a subscription Once you've attached a parameterized metric to a price on a plan, you can provide parameter overrides when [creating](/product-catalog/creating-subscriptions) or [editing](/product-catalog/editing-subscriptions) a subscription. Overrides are set **per price interval**, so different prices on the same subscription can use different values for the same parameter. ### API When creating a subscription, include `price_metric_parameter_overrides`: ```json theme={null} { "customer_id": "customer_abc", "plan_id": "plan_xyz", "price_metric_parameter_overrides": [ { "price_id": "price_123", "metric_parameter_overrides": { "rate_multiplier": 2.5 } } ] } ``` You only need to specify the parameters you want to override. Any parameters not included in `metric_parameter_overrides` use the default value from the metric definition. The same `price_metric_parameter_overrides` field is available when editing a subscription or scheduling a plan change. ### Validation rules * Parameter names must match those defined on the metric. * Override values must match the type of the parameter's default value (numeric overrides for numeric defaults, string overrides for string defaults). * You cannot provide overrides for a price whose metric has no parameter definitions. ## How parameters are resolved When Orb evaluates a parameterized metric — for invoicing, usage dashboards, or alerts — it resolves parameter values in the following order: 1. **Subscription override**: If the subscription's price interval specifies an override for a parameter, that value is used. 2. **Metric default**: If no override is provided, the default value from the metric's parameter definition is used. The resolved values are substituted into the metric's SQL query as literal constants before execution. For example, with `rate_multiplier` resolved to `2.5`: ```sql theme={null} -- What you define: SELECT SUM(amount * {{rate_multiplier}}) FROM events WHERE event_name = 'transaction_processed' -- What Orb executes: SELECT SUM(amount * 2.5) FROM events WHERE event_name = 'transaction_processed' ``` Resolved parameter values are stored on each invoice line item for auditability, so you can always see exactly which values were used for a given billing period. ## Example use cases **Customer-specific rate multipliers** A payments platform charges per transaction, but enterprise customers negotiate custom rates. Define a single metric with a `{{rate_multiplier}}` parameter and override it per subscription: ```sql theme={null} SELECT SUM(transaction_amount * {{rate_multiplier}}) FROM events WHERE event_name = 'payment_captured' ``` Default: `1.0`. Customer A overrides to `0.8` (discounted). Customer B uses the default. **Regional pricing coefficients** A cloud provider charges differently for compute in different regions. Use a string parameter to filter by region and a numeric parameter for the regional rate: ```sql theme={null} SELECT SUM(compute_hours * {{regional_rate}}) FROM events WHERE event_name = 'compute_usage' AND region = {{target_region}} ``` **Configurable thresholds** A data platform charges for storage above a certain threshold. Make the threshold configurable per customer: ```sql theme={null} SELECT GREATEST(MAX(storage_gb) - {{included_gb}}, 0) FROM events WHERE event_name = 'storage_measured' ``` Default: `100`. Customers on a higher tier override to `500`. ## Limits * A metric can have at most **10** parameter definitions. * Parameter names must start with a letter or underscore and can only contain alphanumeric characters and underscores. * Default values must be a number (`int` or `float`) or a string. # Events query explorer Source: https://docs.withorb.com/events-and-metrics/query-explorer The events query explorer lets you run SQL queries directly against your ingested usage events. Instead of scrolling through paginated event lists, you can write queries to filter, aggregate, and analyze your event data in real time. This is useful for debugging integrations, validating metric logic, or answering ad-hoc questions about usage patterns. The query explorer uses the same SQL engine that powers Orb's [billable metrics](/events-and-metrics/construct-metrics), so the queries you write here can inform how you build and refine your metrics. ## Getting started Navigate to **Events > Query** in the Orb dashboard. The explorer opens with a default query that counts events by name: ```sql theme={null} SELECT event_name, COUNT(1) FROM events GROUP BY event_name ``` The query runs automatically against the last 7 days of data. You'll see a results table with the event names in your account and how many of each were ingested. From here, you can modify the query, adjust the time range, and scope results to a specific customer. ## Writing queries Queries follow standard SQL syntax against the `events` table. Every event you've [ingested](/events-and-metrics/event-ingestion) is available as a row in this table, with the following columns: | Column | Type | Description | | ------------------ | -------- | ----------------------------------------------------- | | `event_name` | string | The name of the event (e.g. `transaction_processed`). | | `timestamp_millis` | datetime | When the event occurred, in epoch milliseconds. | | `idempotency_key` | string | The unique identifier for the event. | | `customer_id` | string | The Orb customer ID the event is attributed to. | | `properties` | object | The full properties dictionary sent with the event. | In addition to these built-in columns, every key in your event `properties` is available as a column. If you ingested events with a `region` property, you can reference `region` directly in your query without extracting it from the `properties` object. ### SELECT \* You can use `SELECT *` to return all default columns. This is a convenient starting point when you want to inspect raw events: ```sql theme={null} SELECT * FROM events WHERE event_name = 'api_call' ``` This expands to `event_name`, `timestamp_millis`, `idempotency_key`, `customer_id`, and `properties`. ### Filtering with WHERE Use `WHERE` clauses to filter to the events you care about. You can combine conditions with `AND` and `OR`: ```sql theme={null} SELECT * FROM events WHERE event_name = 'transaction_processed' AND payment_method = 'ach' AND amount_cents > 1000 ``` ### Aggregations The explorer supports the following aggregation functions: | Function | Description | | --------------------------- | ---------------------------------------------- | | `COUNT(...)` | Count of rows. | | `SUM(...)` | Sum of values. | | `AVG(...)` | Average of values. | | `MIN(...)` | Minimum value. | | `MAX(...)` | Maximum value. | | `EARLIEST_BY(value, order)` | Returns the first `value`, ordered by `order`. | | `LATEST_BY(value, order)` | Returns the last `value`, ordered by `order`. | When using aggregation functions, any non-aggregated columns in your `SELECT` must appear in a `GROUP BY` clause: ```sql theme={null} SELECT event_name, COUNT(1), SUM(amount_cents) FROM events WHERE event_name = 'transaction_processed' GROUP BY event_name ``` ### Scalar functions You can use the following functions within expressions: | Function | Description | | ---------------------------------- | -------------------------------------------------------------- | | `COALESCE(...)` | Returns the first non-null argument. | | `LEAST(...)` | Returns the minimum of its arguments. | | `GREATEST(...)` | Returns the maximum of its arguments. | | `CEIL(...)` | Rounds up to the nearest integer. | | `FLOOR(...)` | Rounds down to the nearest integer. | | `ROUND(...)` | Rounds a number to a specified number of decimal places. | | `CONCAT(...)` | Concatenates strings. | | `HOUR_FLOOR_TIMESTAMP_MILLIS(...)` | Floors a timestamp (in milliseconds) to the start of the hour. | ### Operators Standard comparison and logical operators are supported: * **Arithmetic**: `+`, `-`, `*`, `/` * **Comparison**: `=`, `!=`, `>`, `<`, `>=`, `<=` * **Pattern matching**: `LIKE`, `NOT LIKE` * **Logical**: `AND`, `OR`, `IS`, `IS NOT` * **Conditionals**: `CASE WHEN ... THEN ... ELSE ... END` ### DISTINCT Use `SELECT DISTINCT` to return unique rows: ```sql theme={null} SELECT DISTINCT event_name FROM events ``` ### LIMIT You can specify a `LIMIT` to return fewer rows: ```sql theme={null} SELECT * FROM events WHERE event_name = 'api_call' LIMIT 100 ``` The maximum is 1,000 rows per query, regardless of the `LIMIT` you specify. ### Subqueries You can use subqueries in the `FROM` clause for multi-step aggregations: ```sql theme={null} SELECT AVG(daily_count) FROM ( SELECT COUNT(1) as daily_count FROM events WHERE event_name = 'api_call' GROUP BY FLOOR(timestamp_millis / 86400000) ) ``` ## Query controls ### Time range Every query is scoped to a time range. Select from preset ranges (Last 1 hour, Last 1 day, Last 7 days, Last 30 days) or specify a custom start and end date. The maximum time range is **90 days**. The time range is applied as an automatic filter on `timestamp_millis`. You do not need to include it in your `WHERE` clause. ### Customer filter Optionally scope your query to a specific customer. When selected, only events attributed to that customer are included in results. This is useful for debugging a specific customer's usage or validating that events are being attributed correctly. ### Honor amendments When you've amended events for a customer (e.g. deprecated incorrect events or backfilled corrected ones), the **Honor amendments** toggle controls whether the query reflects those changes. When enabled: * Deprecated events are excluded from results. * Backfilled events are included. This requires a customer to be selected, since amendments are scoped to individual customers. When disabled (the default), the query returns all raw events as originally ingested. ## Builder mode If you prefer a visual interface, switch to **Builder** mode using the toggle at the top of the editor. The builder lets you construct queries without writing SQL: * **Columns**: Choose which columns to include in results. * **Filters**: Add conditions with a column, operator, and value. * **Aggregation**: Select an aggregation function (COUNT, SUM, AVG, MIN, MAX) and the column to aggregate. * **Group by**: Choose columns to group results by. * **Time bucket**: Bucket results by minute, hour, or day to spot trends. * **Limit**: Set the maximum number of rows. The builder generates a SQL preview that updates as you configure the query, so you can see exactly what will be executed. You can switch to SQL mode at any time to refine the generated query further. ## Results Query results are displayed in a table below the editor. The status bar shows: * **Row count**: How many rows were returned. * **Query time**: How long the query took to execute. * **Truncated indicator**: Whether results were capped at the limit. You can click any cell to copy its value to your clipboard. ### Datetime display The `timestamp_millis` column and other datetime values are displayed as human-readable timestamps by default. Toggle the **raw** display mode to see the original epoch millisecond values. ### CSV export Click the **Download CSV** button to export the current results. The CSV includes column headers and properly escapes values containing commas, quotes, or newlines. ## Examples **Count events by name over the last 7 days** ```sql theme={null} SELECT event_name, COUNT(1) FROM events GROUP BY event_name ``` **Total transaction volume by payment method** ```sql theme={null} SELECT payment_method, SUM(amount_cents) as total_cents, COUNT(1) as num_transactions FROM events WHERE event_name = 'transaction_processed' GROUP BY payment_method ``` **Hourly API call volume** ```sql theme={null} SELECT HOUR_FLOOR_TIMESTAMP_MILLIS(timestamp_millis) as hour, COUNT(1) as call_count FROM events WHERE event_name = 'api_call' GROUP BY HOUR_FLOOR_TIMESTAMP_MILLIS(timestamp_millis) ``` **Distinct customers who used a feature** ```sql theme={null} SELECT COUNT(DISTINCT customer_id) FROM events WHERE event_name = 'feature_used' AND feature_name = 'advanced_search' ``` **Average compute time, excluding the longest job** ```sql theme={null} SELECT (SUM(compute_ms) - MAX(compute_ms)) / (COUNT(compute_ms) - 1) FROM events WHERE event_name = 'job_completed' ``` **Most recent event per customer** ```sql theme={null} SELECT customer_id, LATEST_BY(event_name, timestamp_millis) as last_event FROM events GROUP BY customer_id ``` ## Limits * Results are capped at **1,000 rows** per query. * Time range cannot exceed **90 days**. * `ORDER BY` is not supported. Use aggregations and `GROUP BY` to organize results. * `JOIN`, `WITH` (CTEs), and `LATERAL` are not supported. Queries can only access the `events` table. * `WHERE` clauses cannot contain aggregation functions. Use subqueries for post-aggregation filtering. # Querying data with SQL Source: https://docs.withorb.com/events-and-metrics/querying-with-sql You often want to query the event data that Orb is storing, to better understand attribution to specific dimensions of your event – even if these dimensions aren't directly relevant to how you bill customers. Since Orb's architecture is designed to run aggregation queries over large sets of event data at query time rather than at ingestion time, the product also exposes this capability directly for ad-hoc querying. For example, you might want to: * View usage for your compute metric broken down by `cluster_name`, where a user may have hundreds of clusters active in a given time period. * Understand how much a specific transaction contributed to a processing charge by point-filtering to a known `transaction_id`. * Provide more detailed analytics, such as breaking down usage and cost data by `bucket_name` and `data_tier` which is included in events you send to Orb. * Build an interactive explorer in your application to view hourly data over a day time range, rather than the default daily views available through Orb's subscription usage and cost APIs. * Provide a view into end customer usage by `api_key` prefix, allowing users to understand the source of API requests. ## Evaluate prices API The [evaluate prices](/api-reference/price/evaluate-multiple-prices) API is Orb's most powerful querying tool, enabling you to slice, dice, and rate event data with SQL-like flexibility. This endpoint allows you to evaluate prices on existing data with advanced filtering and grouping capabilities. ### Core Capabilities The endpoint can evaluate: * **Up to 100 price evaluations** in a single request (existing or inline-defined prices) * **Historical data** up to 100 days in the past (for ingested events) * **Complex filtering and grouping** using computed properties with SQL-like expressions * **Results limited to 1000 rows** to ensure performance **Request Structure:** * **`timeframe_start`** (required): Inclusive lower bound for event timestamps * **`timeframe_end`** (required): Exclusive upper bound for event timestamps * **`customer_id`** (optional): Required when querying ingested events, omit for preview events * **`external_customer_id`** (optional): Alternative to customer\_id * **`price_evaluations`** (required): Array of prices to evaluate with optional filtering and grouping ### Interpreting evaluated amounts Use evaluate prices as a rating and attribution tool: it applies the selected prices to the events in your query and returns grouped quantities and amounts. For complex billing setups, these amounts may not equal finalized invoice totals: * Invoice-level or multi-price adjustments (such as minimums) are applied during invoice calculation. * Some non-linear models depend on period-level context, so there is not always a single fully adjusted "cost per event." If you need finalized totals, use invoice resources or [subscription costs](/api-reference/subscription/fetch-subscription-costs). If you need event-level attribution by dimensions like `user_id` or `api_key`, evaluate prices is the recommended endpoint. ## Key use cases ### 1. Customer-Facing Usage Analytics **Scenario**: Provide your customers with detailed breakdowns of their usage and costs, going beyond standard invoice line items. **Example**: A cloud storage provider wants to show customers their costs broken down by storage tier, region, and time period. ```json theme={null} { "customer_id": "customer_123", "timeframe_start": "2024-01-01T00:00:00Z", "timeframe_end": "2024-01-31T23:59:59Z", "price_evaluations": [ { "price_id": "storage_price_id", "grouping_keys": [ "storage_tier", "region", "floor(hour_floor_timestamp_millis(timestamp_millis) / 86400000) * 86400000" ], "filter": "storage_tier IN ('standard', 'premium')" } ] } ``` This query provides daily usage and cost data segmented by storage tier and region, enabling rich customer dashboards. Orb supports `hour_floor_timestamp_millis(timestamp_millis)` for retrieving the floored hour of usage (UTC) in milliseconds. To get the floored day in milliseconds, use the syntax above. ### 2. Invoice Line Item Auditing **Scenario**: Provide detailed breakdowns of invoice charges to help customers understand exactly what they're being billed for. **Example**: Breaking down a monthly API usage charge by endpoint and customer tier. ```json theme={null} { "customer_id": "customer_456", "timeframe_start": "2024-01-01T00:00:00Z", "timeframe_end": "2024-01-31T23:59:59Z", "price_evaluations": [ { "price_id": "api_usage_price", "grouping_keys": [ "endpoint", "customer_tier" ], "filter": "response_code = '200'" } ] } ``` ### 3. High-Dimensional Usage Analysis **Scenario**: Analyze usage patterns across multiple dimensions that aren't part of your standard billing structure. **Example**: A data processing platform analyzing usage by job type, data source, and processing complexity. ```json theme={null} { "customer_id": "customer_789", "timeframe_start": "2024-01-01T00:00:00Z", "timeframe_end": "2024-01-07T23:59:59Z", "price_evaluations": [ { "price_id": "data_processing_price", "grouping_keys": [ "job_type", "data_source", "complexity_tier", "hour_floor_timestamp_millis(timestamp_millis)" ], "filter": "job_status = 'completed' AND data_size_gb > 1" } ] } ``` ### 4. Transaction-Level Cost Attribution **Scenario**: Understand the cost impact of specific transactions or operations. **Example**: Analyzing the cost contribution of individual database queries or API calls. ```json theme={null} { "customer_id": "customer_101", "timeframe_start": "2024-01-15T00:00:00Z", "timeframe_end": "2024-01-15T23:59:59Z", "price_evaluations": [ { "price_id": "database_operations_price", "filter": "transaction_id = 'txn_abc123' OR query_type = 'complex_join'" } ] } ``` ## Evaluate preview events API The [evaluate preview events](/api-reference/price/evaluate-preview-events) API rates hypothetical events on existing prices in Orb allowing you to build price calculators and "what-if" scenarios off prices in Orb. ### Core Capabilities The endpoint can evaluate: * **Up to 100 price evaluations** in a single request (existing or inline-defined prices) * **Up to 500 preview events** in a single request * **Complex filtering and grouping** using computed properties with SQL-like expressions * **Results limited to 1000 rows** to ensure performance **Request Structure:** * **`timeframe_start`** (required): Inclusive lower bound for event timestamps * **`timeframe_end`** (required): Exclusive upper bound for event timestamps * **`customer_id`** (optional): Required when querying ingested events, omit for preview events * **`events`** (required): Array of preview events * **`external_customer_id`** (optional): Alternative to customer\_id * **`price_evaluations`** (required): Array of prices to evaluate with optional filtering and grouping ## Key use cases ### 1. Price Calculator and Modeling **Scenario**: Build an interactive price calculator that shows potential costs before customers commit to a plan. **Example**: A compute platform wants to let prospects estimate costs based on expected usage patterns. ```json theme={null} { "timeframe_start": "2024-01-15T00:00:00Z", "timeframe_end": "2024-01-15T23:59:59Z", "customer_id": "customer_123", "events": [ { "event_name": "compute_usage", "timestamp": "2024-01-15T10:00:00Z", "properties": { "compute_hours": 100, "instance_type": "large", "region": "us-east-1" } }, { "event_name": "compute_usage", "timestamp": "2024-01-15T14:00:00Z", "properties": { "compute_hours": 50, "instance_type": "medium", "region": "us-west-2" } } ], "price_evaluations": [ { "price": { "name": "Compute Hours", "item_id": "compute_hours_price", "cadence": "monthly", "model_type": "unit", "unit_config": { "unit_amount": "0.10" }, "currency": "USD", "billable_metric_id": "compute_hours_metric" }, "grouping_keys": [ "instance_type", "region" ] } ] } ``` This enables real-time cost estimation as prospects adjust their expected usage parameters. ## Evaluate prices API vs. evaluate preview events API ### Preview Events (Rating Mode) Use preview events when you want to: * Build price calculators * Model "what-if" scenarios * Test new pricing structures * Provide cost estimates before usage occurs **Key characteristics**: * Events are provided directly in the request * No historical data required * Perfect for forward-looking analysis * Limited to 500 events per request * **No customer\_id required** - the API rates the provided events directly ### Ingested Events (Query Mode) Use ingested events when you want to: * Analyze historical usage patterns * Provide detailed billing breakdowns * Audit past charges * Build usage analytics dashboards **Key characteristics**: * Uses events already stored in Orb * **Requires customer\_id** to specify which customer's events to query * Can query up to 100 days of historical data * Supports complex filtering and aggregation * Ideal for retrospective analysis ## Advanced filtering and grouping ### Computed Properties The API supports computed properties for sophisticated data manipulation using Orb's supported functions: #### Time-based Functions ```sql theme={null} -- Group by hour hour_floor_timestamp_millis(timestamp_millis) -- Group by day (with timezone support) floor(hour_floor_timestamp_millis(timestamp_millis) / 86400000) * 86400000 ``` #### Aggregation Functions ```sql theme={null} -- Count events COUNT(*) -- Count distinct values COUNT(DISTINCT region) -- Get earliest value by timestamp EARLIEST_BY(region, timestamp_millis) -- Get latest value by timestamp LATEST_BY(instance_type, timestamp_millis) -- Mathematical operations LEAST(cpu_hours, max_cpu_hours) GREATEST(cpu_hours, min_cpu_hours) -- Rounding ROUND(data_gb, 2) ``` ### Filtering Examples #### Basic Property Filtering ```sql theme={null} -- Exact matches region = 'us-east-1' instance_type = 'large' -- Numeric comparisons cpu_hours > 10 data_gb >= 100 -- Multiple conditions with AND/OR region = 'us-east-1' AND cpu_hours > 10 tier = 'premium' OR tier = 'enterprise' -- IN clauses region IN ('us-east-1', 'us-west-2') tier IN ('premium', 'enterprise') -- Null checks customer_segment IS NOT NULL api_key IS NULL ``` #### Advanced Filtering ```sql theme={null} -- Combining multiple conditions environment = 'production' AND region = 'us-east-1' AND cpu_hours > 50 -- Complex boolean logic (tier = 'premium' AND region = 'us-east-1') OR (tier = 'enterprise' AND cpu_hours > 100) -- Timestamp filtering timestamp_millis >= 1704067200000 ``` ## Response structure and interpretation The API returns results grouped according to your specified `grouping_keys`. The response contains a `data` array where each element represents the results for a specific price, with the following structure: * **price\_groups**: Array of grouped results for this price * **grouping\_values**: The specific values for each grouping dimension * **quantity**: The aggregated metric value for the group * **amount**: The calculated cost for the group * **currency**: The currency of the price * **price\_id**: The ID of the price (for existing prices) * **inline\_price\_index**: The index of the inline price (for inline-defined prices) ### Example Response ```json theme={null} { "data": [ { "price_groups": [ { "grouping_values": [ "us-east-1", "large" ], "quantity": 150.0, "amount": "15.00" }, { "grouping_values": [ "us-west-2", "medium" ], "quantity": 75.0, "amount": "7.50" } ], "currency": "USD", "price_id": "price_123" } ] } ``` ### Multiple Prices Response When evaluating multiple prices, you'll get separate entries in the `data` array: ```json theme={null} { "data": [ { "price_groups": [ { "grouping_values": ["compute"], "quantity": 100.0, "amount": "50.00" } ], "currency": "USD", "price_id": "compute_price_id" }, { "price_groups": [ { "grouping_values": ["storage"], "quantity": 500.0, "amount": "25.00" } ], "currency": "USD", "inline_price_index": 0 } ] } ``` ## Best practices ### Performance Optimization 1. **Limit result sets**: Use filters to reduce the number of rows returned 2. **Choose appropriate time ranges**: Shorter ranges perform better 3. **Optimize grouping**: Avoid excessive grouping dimensions 4. **Use specific filters**: More specific filters improve query performance ### Data Accuracy 1. **Validate preview events**: Ensure preview events match your actual event schema 2. **Test with small datasets**: Start with limited time ranges when exploring 3. **Verify metric definitions**: Ensure your billable metrics align with expectations 4. **Handle edge cases**: Consider how your filters handle null or missing properties ### Integration Patterns 1. **Caching**: Cache results for frequently accessed data 2. **Pagination**: Use time-based pagination for large datasets 3. **Error handling**: Implement robust error handling for API limits 4. **Rate limiting**: Respect API rate limits in high-frequency scenarios ## Common patterns and examples ### Building a Usage Dashboard Combine multiple API calls to build comprehensive usage dashboards: ```javascript theme={null} // Get daily usage trends const dailyUsage = await evaluatePrices({ customer_id: customerId, timeframe_start: startDate, timeframe_end: endDate, price_evaluations: [ { price_id: priceId, grouping_keys: ["day_floor_timestamp_millis(timestamp_millis)"] } ] }); // Get usage by service const serviceBreakdown = await evaluatePrices({ customer_id: customerId, timeframe_start: startDate, timeframe_end: endDate, price_evaluations: [ { price_id: priceId, grouping_keys: ["service_name"] } ] }); // Get top consumers const topConsumers = await evaluatePrices({ customer_id: customerId, timeframe_start: startDate, timeframe_end: endDate, price_evaluations: [ { price_id: priceId, grouping_keys: ["user_id"], filter: "user_id IS NOT NULL" } ] }); ``` ### Price Comparison Tool Compare costs across different pricing models: ```javascript theme={null} const priceComparison = await evaluatePrices({ timeframe_start: startDate, timeframe_end: endDate, events: previewEvents, price_evaluations: [ { // Current pricing price_id: "current_price_id" }, { // Alternative unit pricing price: { name: "Alternative Unit Pricing", item_id: "alt_unit_price", cadence: "monthly", model_type: "unit", unit_config: { unit_amount: "0.05" }, currency: "USD", billable_metric_id: "api_calls_metric" } } ] }); ``` The evaluate prices API transforms how you can interact with your billing data, enabling everything from simple usage queries to sophisticated pricing analysis. By leveraging its filtering and grouping capabilities, you can build rich, customer-facing analytics and powerful internal tools for pricing optimization. # Backfill and amend events Source: https://docs.withorb.com/events-and-metrics/reporting-errors Orb provides powerful and audit-safe mechanisms to handle reporting errors from your system, downtime or lag in your infrastructure, and other cases where you need to revise data that has been reported to Orb. Amendments on *usage data*, rather than simply editing invoice line items, provide increased transparency in real-time to you and your customers. They also allow you to adjust the underlying dataset in a traceable way, relying on Orb to calculate the effects of the new data on the invoice instead of doing this manually. Orb provides a backfill API that also allows you to optionally replace or deprecate existing events in a timeframe. You can use this in order to: * Decrease historical usage consumption because of degraded service availability in your systems * Account for gaps from your usage reporting mechanism * Make point-in-time fixes for specific event records, while retaining the original time of usage and associated metadata These APIs are designed with two explicit goals: * Amendments and backfills are **always audit-safe**. For auditing and data fidelity purposes, Orb never overwrites or permanently deletes ingested usage data. * These operations **always preserve data consistency**. Orb prevents you from ending up in a state where data partially reflects an amendment operation, where it can be difficult to decipher the state of a customer’s usage events. ## Reporting grace period Orb supports real-time ingestion of usage data, preventing you from having to perform any batching or aggregation logic over your events. However, it’s common for your *reporting* infrastructure to lag behind *user actions* that correspond to usage, especially when usage is reported asynchronously to Orb. Orb always honors the `timestamp` property of the event, which represents *when the action took place* for billing purposes and is important in order to place the usage in a specific billing period. To account for reporting delays, your system can report events to the Orb API up to 12 hours after the `timestamp`, which is called the *grace period* for event reporting. This is especially important towards the end of a billing cycle, because a pending invoice might still be subject to changes for 12 hours after the end of the period and will not be finalized until the grace period has passed. The grace period is configurable at an account-wide level. ## Timeframe-based amendments In cases where all events in a specific time range need to be overwritten, Orb [allows you to provide](/api-reference/event/create-backfill) an exact timestamp range and a new set of events that should be used for billing purposes. Old events are marked as archived; they can still be queried via Orb’s APIs but Orb will not use them for any billing functionality. Although events that are part of a backfill require an idempotency key, this idempotency key is not meant to be compared with existing non-backfill events. This allows you to send in the same idempotency key for the same event set if you're re-ingesting a previously ingested range of events. However, this means that you should not rely on the idempotency guarantee across normal event ingestion and backfills. A timeframe based amendment is ideal in scenarios such as downtime in your system where critical functionality was unavailable and you want to clear any usage that happened for affected customers. Note: We do not recommend backfilling events before the beginning of the current billing period for customers using the credit ledger because Orb is not able to reflect changes to deductions as a result of backfilled events in issued invoices. ## Amending single events In order to change the structure of a *single known event*, Orb [provides a `PUT` verb on event resources](/api-reference/event/amend-event). For a given event ID (which is the same as an event’s `idempotency_key`) this endpoint can be used to change any part of the event, excluding its `timestamp` and associated customer ID. As with timeframe-based amendments, Orb never deletes the original event, but it is marked as archived and not used for billing purposes. A single-event amendment is ideal in cases where there is a specific dispute (e.g. a transaction needs to be reversed). In cases where the *status* of a resource changes over time, Orb recommends sending an *additional* usage event rather than amending the existing one in order to maintain both pieces of state. ## Deprecate a single event Orb allows you to [deprecate an event](/api-reference/event/deprecate-event) given an event ID, which means that it will no longer be counted towards an active subscription's billing cycle. As with all amendments functionality, the original event will be archived and not permanently deleted. # Segment integration Source: https://docs.withorb.com/events-and-metrics/segment-integration With Segment, you can easily integrate your sources with the Orb destination for event ingestion. The Orb destination currently supports track calls only. The [track call](https://segment.com/docs/connections/spec/track/) is how you record any actions your users perform, along with any properties that describe the action. This is typically used to trigger events based on user actions. At Orb, each track call sent to our destination from any of your sources is ingested as a single usage event. ### Overview 1. From the Destinations catalog page in the Segment App, click **Add Destination**. 2. Search for "Orb" in the Destinations Catalog, and select the "Orb" destination. 3. Choose which Source should send data to the "Orb" destination. 4. Go to the [Orb dashboard](https://app.withorb.com), create a new API key from the configuration's page or reuse an existing one. We recommend creating a new API key for this integration. 5. Enter the "API Key" in the "Orb" destination settings in Segment. 6. Fill out settings for how to map event properties (see below for configuration defaults). ### Configuring event transformations Orb's destination supports settings for any field mappings from within your `properties` object passed to the track call, as well as configurable fields for `event_name`, `idempotency_key`, and `external_customer_id`. Orb does not allow specifying a field for `timestamp` and will honor the Segment event `timestamp`. Defaults are available for certain top level fields: | Orb event field | Default Segment event field | Segment connection settings name | | ---------------------- | ------------------------------ | -------------------------------- | | `event_name` | `event` | Event name field | | `idempotency_key` | `messageId` | Idempotency key field | | `external_customer_id` | No default | External customer ID field | | `timestamp` | `timestamp` (Not configurable) | | For example, if your track call from a Segment source looks like: ```js theme={null} analytics.track({ event: "payment_confirmed", userId: "external_customer_id", properties: { amount: 100.00, currency: "USD", confirmation_time: "2022-05-11T21:33:13.1652304793Z" } }); ``` and your desired event properties format looks like: ```js theme={null} { "total_charge": 100.00, "denomination": "USD", "confirmation_time": "2022-05-11T21:33:13.1652304793Z" } ``` then the following `Properties mapping fields` configuration for your destination will yield the desired event format: Along with the defaults for the other fields, and the `External customer ID field` set to `userId`, your ingested event would look like: ```js theme={null} { "idempotency_key": "segment.messageId", "event_name": "payment_confirmed", "external_customer_id": "external_customer_id", "timestamp": "segment.timestamp", "properties": { "total_charge": 100.00, "denomination": "USD", "confirmation_time": "2022-05-11T21:33:13.1652304793Z" } } ``` ### Configuration constraints Orb enforces a type constraint on the `Properties mapping fields`, requiring all keys and values in this configuration setting to be strings. In the event that we receive a request with a different type for any of the fields in this setting, we will raise a `400 Request validation error` error and Orb will not ingest the Segment event. # Advanced metrics Source: https://docs.withorb.com/extensibility/advanced-metrics Orb does not limit you to a fixed set of operators over your raw events when defining your billable metrics. For the overview of billable metrics, see [creating usage metrics](/events-and-metrics/construct-metrics). ### Computed properties The Orb web interface allows you to specify a SQL clause when properties of the original events do not suffice to assemble your metric. For example, you might want to multiply a property by a constant factor, combine two properties of the event, or use a function like `COALESCE` to handle `null` values. When composing your billable metric with a computed property, Orb shows you a preview of the computed property across the events displayed. ### SQL support Traditional billing systems support a rigid set of operators over the reported metric, such as `LAST` or `MAX`, not allowing you to perform sophisticated transforms or usage calculations. Orb is designed to be extensible at its core, and allows you to define metrics fully in SQL. Orb’s SQL engine runs in real-time over your reported events stream, while still providing you **SQL syntax support**. SQL queries run over the entirety of events in the billing period, and not just an incremental slice of reported events, so you never have to worry about keeping track of intermediate stream state. For example, customers use Orb’s SQL metrics layer to: 1. Use `CASE` statements and conditionals to construct sophisticated compute units that bill differently for each event. 2. Use sub-queries for more sophisticated aggregations, such as calculating average volume over the month *excluding* the top *n* days of usage. 3. Transform and scale event properties before they are used for billing purposes. ### Examples The following examples illustrate the sort of computations you can perform with Orb’s SQL metrics layer, assuming that you have the events which match the query (the schema of the event isn't shown here, but feel free to reach out to the Orb team with help modeling both your events schema and the resulting metric). **Sum of maximum network egress for every cluster every hour** ``` SELECT SUM(network_egress_per_cluster_per_hour) FROM ( SELECT MAX(value) as network_egress_per_cluster_per_hour FROM events WHERE event_name ='network_egress_db' GROUP BY cluster_id, HOUR_FLOOR_TIMESTAMP_MILLIS(timestamp_millis) ) ``` **Increase in storage over the course of the month** ``` SELECT MAX(storage_gb) - EARLIEST_BY(storage_gb, timestamp_millis) FROM events WHERE event_name = 'storage_measured' ``` **Average run time, taking out the longest job** ``` SELECT (SUM(run_time_ms) - MAX(run_time_ms)) / (COUNT(run_time_ms) - 1) FROM events WHERE event_name = 'job_run' AND job_type = 'identity_verification' ``` **Average number of jobs per hour taking more than 15 seconds** ``` SELECT AVG(long_jobs_per_hour) / COUNT(DISTINCT hr) FROM ( SELECT HOUR_FLOOR_TIMESTAMP_MILLIS(timestamp_millis) AS hr, COUNT(execution_id) as long_jobs_per_hour FROM events WHERE event_name = 'execution_completed' AND execution_state = 'success' AND execution_time_ms > 15 * 1000 GROUP BY HOUR_FLOOR_TIMESTAMP_MILLIS(timestamp_millis) ) ``` ## Executing a metric Orb is responsible for keeping your metric value automatically up to date throughout the billing cycle. All you have to do is define it, and Orb executes it in the context of a customer and billing period. # Custom pricing Source: https://docs.withorb.com/extensibility/custom-pricing Orb is a uniquely *extensible* billing platform, supporting any usage-based business. With this focus, Orb allows you to *deploy your business model* onto its data infrastructure, rather than limiting your implementation to a pre-defined set of constraints determined by our product roadmap. ## Custom pricing platform Orb natively provides an array of standard pricing models which are common across a wide range of usage-based businesses. These vary in complexity, from a linear unit price per quantity of usage to a *tiered* model, where a marginal unit can be cheaper than previous units. When the standard pricing models do not suffice for your use case, please contact the Orb team and we can help to unblock you. Conceptually, a pricing function is anything which translates a quantity value like *number of API calls* to an amount that can be charged. These pricing functions are often unique to your customer relationship, and it can be a point of friction when your billing provider does not provide support for how you bill your customers. The input to a pricing function is usually a single aggregate value over a timeframe (e.g. a billing period), but some pricing functions require a value *per event* in order to calculate an overall amount. Suppose our business offers storage services, and we want to charge our customers a different price based on their utilization, with the caveat that we’d like to enforce a *maximum* price which is different per tier. We’ll call this model the *capped tiers* model. Here’s how an specific pricing configuration might look for *capped tiers*: | Total storage utilization | Price per gb-hr | Maximum total price | | ------------------------- | --------------- | ------------------- | | **0 Gb-hr - 20 Gb-hr** | \$5 | \$50 | | **20 Gb-hr - 100 Gb-hr** | \$3 | \$100 | | **100 Gb-hr +** | \$2 | \$300 | Note that the notion of a *maximum price per tier* is not part of the pre-built tiering model with Orb, and in other billing systems you would not be able to implement *capped tiers*. To implement this custom model in Orb, two steps are required. First we determine the *configuration* parameters for the pricing structure and the corresponding schema. Since we’d like to be able to set up many different prices which all conform to the capped tiers *schema*, we need to allow configuring a flexible number of tiers and two parameters per tier: the unit price, and a maximum total price. Orb has first-class UI and schema constructs in the pricing platform for the “tier” concept, which provide out of the box data entry validation for tier ranges. We then need to *implement* the pricing function given a quantity and the price’s configuration parameters. In this case, simple pseudocode might look like: ```python theme={null} def price(quantity: Decimal, capped_tiers_config: Config) -> MonetaryAmount: descending_tier_configs = sorted(capped_tiers_config.tier_configs, key=tier_config.min, reverse=True) for tier_config in descending_tier_configs: if quantity >= tier_config.min: return min(tier_config.max_total, tier_config.unit_price * quantity) return 0 ``` Once this pricing model has been deployed on to the platform, it’s treated like a pre-built pricing function: 1. The pricing model is available for existing and new plans, and is configurable via the Orb UI. For the example above, non-technical users could create and modify plans with custom tier configurations without any other setup. 2. Similar to any other price, a custom price’s configuration can be modified when creating a subscription to represent custom deals. 3. Orb will run your pricing function code to generate alerts as well as usage graphs for each Customer. # AR aging Source: https://docs.withorb.com/financial-operations/ar-aging ## Introduction Orb’s [Accounts Receivable Aging (AR aging) report](https://app.withorb.com/reports/ar_aging) breaks down outstanding receivables invoiced through Orb by how long they’ve been unpaid, helping finance teams manage collections and end-of-month reporting.  Use this report to: * Assess and forecast cash flow * Monitor customer payment behavior * Support bad-debt and allowance calculations * Prepare financial statements as part of your month-end close ## Key components **Standard aging buckets** Orb groups your issued but unpaid invoices into 5 buckets: | Bucket | Definition | | ------------------- | ------------------------------------------------------------- | | Current | Issued invoices that are not past the due date | | 1-30 days past due | Issued invoices that are between 1-30 days past the due date | | 31-60 days past due | Issued invoices that are between 31-60 days past the due date | | 61-90 days past due | Issued invoices that are between 61-90 days past the due date | | 91+ days past due | Issued invoices that are over 91 days past the due date | **Date picker** Show outstanding invoices issued on or before a specific “as of” date (through the end of the day in the customer’s timezone). Select a historical date to report on AR balances at a point in time for end-of-month close, or select the current date to follow up with customers for collections. Aragingdatepicker2 Pn **Multi-currency** If you bill in multiple invoicing currencies, select which currency you’d like to see your AR aging report from the currency dropdown. **Reporting configurations** Pivot or filter data by a customer, invoice, or plan for additional reporting granularity and insights.  Note: only subscription generated invoices will be attributed to a “Plan”. There is no “Plan” attribute for [one-off invoices](https://docs.withorb.com/invoicing/one-off-invoice-creation#one-off-invoices) for ad-hoc charges your customer may incur. Aragingconfigs Pn **Extending the AR aging report** Display additional metadata and download your AR aging report from Orb to CSV for further analysis. Note: invoice status and plan status are only available in the “as of” current date view for collections. Aragingexport Pn | Column name | Description | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Customer | The name of the customer. | | Customer ID | The Orb ID of the customer. | | External customer ID | The external ID of the customer. | | Invoice | The identifying invoice number. | | Invoice ID | The ID of the invoice. | | Payment failed at | The time the invoice payment failed. | | Plan | The name of the plan. | | Plan ID | The ID of the plan. This value is null for one-off invoices. | | Plan status | The status of the plan. Available options: active, archived. | | Current | The total invoice amount in the “current” aging bucket. These are issued invoices that are not past the due date, as of the report date. | | 1-30 days past due | The total invoice amount in the “1-30 days past due” aging bucket. These are issued invoices that are between 1-30 days past the due date, as of the report date. | | 31-60 days past due | The total invoice amount in the “31-60 days past due” aging bucket. These are issued invoices that are between 31-60 days past the due date, as of the report date. | | 61-90 days past due | The total invoice amount in the “61-90 days past due” aging bucket. These are issued invoices that are between 61-90 days past the due date, as of the report date. | | 91+ days past due | The total invoice amount in the “91+ days past due” aging bucket. These are issued invoices that are over 91 days past the due date, as of the report date. | | Total | The total invoice amount that has been issued, but not paid. | ## Worked examples ### Reading an AR aging report Consider this AR aging snapshot for a SaaS company as of March 31: | Customer | Current | 1-30 | 31-60 | 61-90 | 91+ | Total | | --------- | ----------- | ----------- | ----------- | ----------- | ----------- | ------------ | | Acme Corp | \$5,000 | \$0 | \$0 | \$0 | \$0 | \$5,000 | | Beta Inc | \$0 | \$2,500 | \$0 | \$0 | \$0 | \$2,500 | | Gamma LLC | \$0 | \$0 | \$3,200 | \$0 | \$0 | \$3,200 | | Delta Co | \$0 | \$0 | \$0 | \$1,800 | \$4,500 | \$6,300 | | **Total** | **\$5,000** | **\$2,500** | **\$3,200** | **\$1,800** | **\$4,500** | **\$17,000** | **Interpreting this data:** * **Acme Corp** (\$5,000 Current): Invoice issued recently, not yet due. No action needed. * **Beta Inc** (\$2,500 in 1-30): Slightly past due. Standard reminder email may be appropriate. * **Gamma LLC** (\$3,200 in 31-60): Requires follow-up. Consider escalating to a phone call or payment plan discussion. * **Delta Co** (\$6,300, mostly 91+): High risk. The \$4,500 in 91+ may require escalation to collections or evaluation for bad debt reserve. ### Cash flow forecasting Using the aging distribution, you can estimate collection timing: | Bucket | Amount | Typical collection rate | Expected collections | | ---------- | ------- | ----------------------- | -------------------- | | Current | \$5,000 | 98% within 30 days | \$4,900 | | 1-30 days | \$2,500 | 90% within 30 days | \$2,250 | | 31-60 days | \$3,200 | 75% within 30 days | \$2,400 | | 61-90 days | \$1,800 | 50% within 60 days | \$900 | | 91+ days | \$4,500 | 25% eventually | \$1,125 | **30-day cash forecast**: \~\$9,550 expected collections **Total expected recovery**: \~\$11,575 of \$17,000 (68%) Collection rates vary significantly by industry, customer segment, and payment terms. Track your actual collection rates over time to improve forecast accuracy. ### Impact of credit notes on AR aging When you issue a credit note against an unpaid invoice, the AR aging report reflects the reduced amount due: **Before credit note:** | Customer | Invoice | Amount Due | Bucket | | --------- | ------- | ---------- | --------- | | Acme Corp | INV-001 | \$10,000 | 1-30 days | **After issuing \$3,000 credit note:** | Customer | Invoice | Amount Due | Bucket | | --------- | ------- | ---------- | --------- | | Acme Corp | INV-001 | \$7,000 | 1-30 days | The credit note reduces the collectible amount. If the credit note covers the full remaining balance, the invoice is marked as paid and removed from AR aging entirely. ## Report methodology **Invoices "as of"** Orb shows invoices “as of” a certain date that meet the following criteria: * Collectible as of that date (invoice status = issued) * Unpaid, or payment failed * Created, or backdated with an invoice date as of that date **Payment timing** Orb records the paid date of an invoice as: | Invoice type / payment method | Payment timestamp | | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | \$0 invoice | [\$0 invoices](https://docs.withorb.com/invoicing/structure#invoice-states) are automatically marked as paid upon issuance | | [Auto-collected](https://docs.withorb.com/invoicing/payments#auto-collection) | Payment is recorded when the default payment method is successfully charged | | Manually “[marked as paid](https://docs.withorb.com/api-reference/invoice/mark-invoice-as-paid#mark-invoice-as-paid)” | Payment is recorded on the payment received date. This field is distinct from the “marked as paid” action date. | | Invoices with small amounts below Stripe’s minimum charge threshold | [Small invoice amounts](https://docs.withorb.com/invoicing/payments#automatic-balance-carryover-for-small-invoices) are automatically marked as paid upon issuance (and carried forward as a negative customer balance) | | Fully credited invoice ([credit note](https://docs.withorb.com/invoicing/credit-notes#issuing-an-adjustment-credit-note) for full amount) | If the remaining amount due is \$0, they are automatically marked as paid upon credit note issuance | Partial payments are not yet supported in Orb at this time. **Invoice details** * Invoice amounts always represent the final amount due, inclusive of credit notes, tax and adjustments * Voided invoices are excluded from the AR aging report relative to the void action date **Sync timing** Invoice data in Orb reports is refreshed regularly, and is usually available in the report within 20 minutes. Actual processing times may be longer during high volume periods. **Synced invoices** For those invoicing outside of Orb, finalized invoices from Orb are [synced and issued from your third-party invoicing provider](https://docs.withorb.com/invoicing/structure#invoice-states). These invoices have a status of  “synced” in Orb, and are included in the AR aging report. To change the invoice status to “paid” and reflect collections in the Orb AR aging report, you must manually mark these invoices as “paid” in Orb. # Collections Automations Source: https://docs.withorb.com/financial-operations/automations # Introduction Collections Automations are Orb's self-serve system for recovering revenue from unpaid invoices. It lets you build multi-step workflows that combine payment retries, customer emails, and webhooks — targeted at exactly the invoices you choose, and triggered at any point in the invoice lifecycle. Set up automations to * Scale away the manual effort of chasing high-volume PLG/self-serve customers – without adding AR headcount * Personalize white-glove outreach to marquee Enterprise customers * Surface an early warning to the account team before a strategic renewal lapses * Let Finance/RevOps own and optimize directly, without filing engineering tickets for iteration * Route escalations straight into existing tools (Slack, CRM) so AR isn’t siloed in a billing dashboard * Trigger downstream actions (entitlements, product upsells) to control the entire journey This feature is available to all Orb customers on our **Enterprise** Plan. Contact Orb to learn about access and upgrade options. # Key components ## Automation schedules The schedule is a reusable template that defines exactly what happens to an unpaid invoice, and when. Collections1 Each schedule is made up of * **Invoice criteria (“If”)** – determines the cohort of invoices this automation is applied to when issued. * **Automation steps (“Then”)** – configurable triggers for actions including payment attempts and retries, reminder emails, and webhooks. * **Timeline offsets** – determines the days before or after the invoice’s due date. This is the “anchor” point. * **Priority** – determines which automation applies to an invoice, if it meets the criteria for multiple automations. Collections3 ### Supported actions 1. **Payment attempts and retries** *Eligible payment method must be configured before invoice issuance to take effect.* Configure initial payment attempts using the payment method on file to automate kicking off the collections process. In the event of payment failure, configure additional attempts to increase changes of recovery due to things like funding availability. 2. **Emails** Notify the customer directly with strategic reminders to increase chances of collection. Customize email content in your brand and voice by creating additional email templates. 3. **Webhooks** A webhook fires an event from Orb that can be used to hook into additional systems when a step executes, independent of whether that step also included other actions. Use this to route collections activity into tools Orb doesn't natively integrate with, including internal email services, sales/CRM workflows, reporting, or entitlements managers. ## Email templates Create and save re-usable content to send as part of an automation. To help draft new email content, Orb provides editable preset templates to get you started. From there, add your own content, voice, and tone to the structured email sections. The subject, preview text, header, body, and footer are all customizable. Customizations to the email layout, support for additional branding, and media attachments are not supported at this time. Templates also support a fixed set of dynamic variables – things like customer name, amount due, retry date, and card details — which Orb resolves and drops in at send time, so the same template can serve every customer without manual customization per invoice. Collections2 ## Collections settings Collections4 At the top of Settings > Invoices > Collections Automations, two account-level controls determine whether email and payment actions can take effect. 1. **Send email notifications** governs whether Orb sends any invoice-related email — this covers the baseline transactional emails (invoice issued, invoice paid) as well as any templated emails defined in your automation schedules. If this is off, schedules can still run their retry and webhook actions, but any email steps are effectively silenced. 2. **Enable payment attempts** governs whether Orb charges the customer's payment method at all — both the initial charge when an invoice becomes due, and any scheduled retries after a failure.  For long-time Orb users, this is the consolidated setting that replaced the previously separate auto-collection and retry-failed-payments controls. If this is off, invoices must be paid manually; schedules can still send emails and webhooks, but won't attempt to collect payment. Together, these two toggles act as master switches for the two customer-facing action types (emails and payment retries) — you can turn either off account-wide without having to touch or disable your individual schedules. Standalone webhooks on `Active` schedules will fire independently of these settings. ## Invoice detail Every invoice that has ever had an automation applied to it shows an Schedule tab on its detail page, listing exactly which schedule was applied to it (e.g., "Enterprise customer follow up") and every step in that schedule. Use this to confirm automation activity, and verify what's happened and what's still coming for that specific invoice. If an invoice is actively in an automation, a banner will appear at the top of the page. Once the automation elapses, the Schedule tab persists as a historical record. Invoices that never met the criteria for an active automation at time of issuance won't show this tab at all. Collections5 # Create an automation 1. **Draft your email templates** Create your email templates before building the schedule itself, so you can plug in finished content rather than working around placeholders. Go to **Settings > Invoices > Collections Automations > Email templates**, start from one of Orb's preset templates, and customize the subject, preview text, header, body, and footer. Preview and send yourself a test before publishing. 2. **Create a new schedule** From **Settings > Invoices > Collections Automations**, select **New schedule** and give it a descriptive name (e.g., "Enterprise customer follow up") so it's identifiable later on individual invoices and in your priority list. 3. **Define your cohort (“If”)** Define the invoice criteria that determine which invoices this schedule applies to — plan, payment method type, customer attributes, and more. **“Sticky” schedules.** This criteria is evaluated once, at invoice issuance, to decide whether an invoice is enrolled in this schedule. If the invoice's attributes change later (e.g., payment method switches from ACH to card), it stays on its original schedule rather than jumping to a different one. 4. **Build out your steps and choose your actions (“Then”)** For each step, set: * Timeline offset — the anchor point in days before or after the invoice due date for each action. * Actions — one or more of: a payment attempt/retry, an email (referencing a template from step 1), or a standalone webhook. **A note on payment processing time** Your payments provider controls how long it takes to respond to a payment attempt. Some payment methods — particularly ACH and wire transfers — can take significantly longer to process than card payments. As a result, Orb may not receive a success or failure response from your PSP before the next scheduled step in the automation fires. In these cases, the next step will run based on the invoice's status at that time, which may not yet reflect the outcome of the prior payment attempt. 5. **Set priority** If you have more than one schedule, set this schedule's priority relative to the others. When an invoice matches the criteria for multiple schedules, Orb applies whichever matching schedule has the highest priority. 6. **Turn on account controls** Confirm the two account-level toggles in Settings > Invoices > Collections Automations are on: Send email notifications and Enable payment attempts. Without these, your schedule's email and/or retry steps won't take effect (standalone webhooks fire regardless of these toggles). 7. **Save and activate** This applies to future invoices issued *after* the automation’s activation time. # Manage an automation 1. **Updating priority** When more than one schedule's criteria could match the same invoice, priority determines which one wins. From the schedule list in Settings > Invoices > Collections Automations, reorder your schedules to control this — Orb evaluates them top-down and enrolls the invoice in the first (highest-priority) schedule it matches.  Reordering priority only affects future enrollment decisions; it doesn't move invoices already enrolled in a schedule. 2. **Muting an automation** Muting a schedule temporarily suppresses its actions without cancelling it outright — useful for giving a specific account a grace period (e.g., a VIP customer mid-negotiation, or a known dispute in progress) without losing the schedule's place in its timeline.  Steps that would have fired while muted are skipped, not queued up. Unmuting resumes the schedule from wherever it currently stands, rather than replaying everything that was missed.  3. **Cancelling an automation** Cancelling a schedule permanently stops it on that invoice — unlike mute, there's no resuming it later. Any remaining steps are marked cancelled and will never fire, even if you re-enable automations for that invoice down the line. Collections6 4. **Editing existing automation schedules** If you edit a schedule, those changes apply immediately to any invoice currently in progress on that schedule — not just to invoices issued in the future. For example, if you update the email copy on a step that *hasn't fired yet*, invoices already partway through that schedule will receive the new copy when that step runs, not the copy that existed when they were enrolled. # Webhooks Webhooks are system-to-system notifications that can be used to operationalize customer-side workflows like triggering Slack notifications, sending custom emails, and reporting. A webhook is fired for every **step** in a collections schedule. The webhook payload includes: 1. Invoice information 2. Schedule properties 3. Action entries, by `action_type` ## Invoice information | **Field** | **Description** | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `id` | Primary reference | | `customer_id` | Primary reference | | `external_customer_id` | Primary reference | | `subscription_id` | Primary reference | | `customer_name` | Human-readable customer name. Used by Orb's own email templates (Hi `{{customer_name}}`, ...); consumers replacing emails need it for the same purpose. | | `invoice_number` | Human-readable identifier for the invoice (e.g., INV-2026-00417). | | `status` | Current invoice status (issued, paid, void, ...) at webhook emit time. | | `amount_due` | The amount owed. Same field Orb renders into (\$1,250.00) was due on `{{due_date}}`. | | `currency` | Currency for the amount due. | | `issued_at` | When the invoice transitioned to issued. Useful for "X days past due" calculations. | | `invoice_date` | Invoice date. | | `due_date` | Payment due date. Anchors the automation timeline. | | `memo` | Optional invoice memo, when set. Mirrors what Orb's templates display in the email body. | | `hosted_invoice_url` | Link the consumer can include in their own emails or share directly with the customer. | | `payment_method_last_four_digits` | Last four digits of the payment method that would be used for retry (or, for non-retry steps, the customer's primary payment method on file). Null if no payment method is set. Required for "your card ending in 4242 was declined" copy in replace-Orb's-emails flows. | **Example** ```json theme={null} { "id": "gzaNJNM27aT7HXGr", "created_at": "2026-05-27T14:32:18.421Z", "type": "invoice.automation_schedule_step_executed", "invoice": { "id": "cvXhvjRhhUQhpu9f", "customer_id": "XqfqQhY5fp8Gz74D", "customer_name": "Acme Corp", "subscription_id": "aekgWvRs8HaesKja", "invoice_number": "INV-2026-00417", "status": "issued", "amount_due": "1250.00", "currency": "USD", "issued_at": "2026-05-15T14:30:00Z", "invoice_date": "2026-05-15T00:00:00Z", "due_date": "2026-05-30", "memo": "Net 30 terms. Please remit by due date.", "hosted_invoice_url": "https://pay.withorb.com/cvXhvjRhhUQhpu9f", "payment_method_last_four_digits": "4242" }, "properties": { /* see structure below */ } } ``` ## Schedule properties | **Field** | Type | Description | | ----------------------------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `automation_schedule_template_id` | string | The automation schedule template | | `automation_schedule_template_name` | string | Human-readable template name at creation time. | | `label` | string | Routing key for downstream handlers. Set by the rule author if provided, otherwise auto-generated to encode day offset + actions (e.g., day\_4\_after\_due\_retry\_payment, day\_14\_after\_due\_notification). Always present.
label is **step-level**, not action-level: it lives in properties and applies to the whole step regardless of which actions (if any) ran. | | `scheduled_at` | datetime | When this step was supposed to run. | | `executed_at` | datetime | When it actually ran (may differ from scheduled\_at if jobs were delayed). | | `actions` | array | One entry per action that ran in this step. Empty array for pure notification steps. See entry shapes below. | ## Action entries within the `actions` array ### `action_type`: `send_email` The entry is always present in the actions array when a `send_email` action was configured in the step, regardless of whether an email was actually sent. Use it for logging (when `sent: true`), or use it to fire your own email service (when `sent: false`). The envelope's invoice block already carries every variable Orb's templates render against, so you don't need additional context from the email entry itself to produce the equivalent email. | **Field** | **Purpose** | | ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `recipient` | Billing email the message was (or would have been) sent to. | | `sent` | `true` if Orb delivered the email; `false` if Orb did not send because account-level email notifications are disabled. Critical for consumers replacing Orb's emails so they don't double-send. | ```json theme={null} { "action_type": "send_email", "recipient": "billing@acme-corp.test", "sent": true } ``` ## `action_type`: `retry_payment` This entry signals that **the retry attempt was executed**: the job ran and the request was dispatched to the payment provider. The step webhook is emitted after the retry resolves, so the entry also reports the `outcome` of the attempt. Final payment outcome also flows through the existing `invoice.payment_succeeded` or `invoice.payment_failed` webhooks when the provider's async result arrives. The automation step webhook tells consumers *what the automation did* (it attempted a retry, and how it resolved). The payment webhooks tell them *what the payment outcome was* (success or failure, with decline reason for templates like `payment_retry_failed`). Correlate the automation attempt with its eventual payment outcome via two keys (whichever is more useful for your flow): * **By specific attempt**: `payment_provider_transaction_id` is the same field that appears on `invoice.payment_succeeded` / `invoice.payment_failed`. Match on this for unambiguous attempt-to-outcome pairing. * **By invoice**: both webhooks carry the full invoice object. If an invoice has only one in-flight retry, matching on `invoice.id` alone is sufficient. | Field | Description | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `payment_transaction_record_id` | Orb's internal record ID for this retry attempt. Stable Orb-side reference for API lookups. | | `payment_provider` | Which gateway ran the retry. Matches the field name in `invoice.payment_succeeded` / `invoice.payment_failed`. | | `payment_provider_transaction_id` | The gateway's own transaction ID (e.g., `pi_xxxx`). Same field name and value as in `invoice.payment_succeeded` / `invoice.payment_failed`, so consumers can match a specific automation retry to its specific payment outcome event. | | `amount_attempted` | The amount the retry attempted to charge. Usually equals `invoice.amount_due` but can diverge in edge cases (partial collection, balance application reducing the charge). | | `currency` | Currency for the amount attempted. | | `outcome` | `succeeded`, `failed`, or `skipped`. `skipped` means no charge was dispatched — e.g., the invoice was paid or voided before the step ran, account-level payment attempts are disabled, or no payment method is on file. | | `failure_reason` | The payment provider's failure message when the retry failed (e.g., a decline reason). `null` otherwise. | **Example** ```json theme={null} { "action_type": "retry_payment", "payment_transaction_record_id": "9soezSxkLTW3nemS", "payment_provider": "stripe", "payment_provider_transaction_id": "pi_3OAbCdEfGhIjKlMn", "amount_attempted": "1250.00", "currency": "USD", "outcome": "failed", "failure_reason": "Your card was declined." } ``` ## Migrating from legacy dunning webhooks Collections Automations supersedes Orb's legacy dunning system and its webhook family (`invoice.dunning_schedule_created`, `invoice.dunning_schedule_step_executed`, `invoice.dunning_schedule_ended`, `invoice.dunning_schedule_reset`), which is deprecated. Orb will continue emitting the legacy dunning events for automation activity, so existing consumers keep working while they migrate. These events are **deprecated** and will be discontinued after a deprecation window. | Automation activity | New event | Legacy event also emitted | | ------------------------------------------------------ | ------------------------------------------- | ---------------------------------------- | | Invoice enrolled in a schedule | — | `invoice.dunning_schedule_created` | | Schedule step executed | `invoice.automation_schedule_step_executed` | `invoice.dunning_schedule_step_executed` | | Schedule ended (paid, voided, cancelled, or exhausted) | — | `invoice.dunning_schedule_ended` | A few things to know: * The two step events describe the **same step execution**. If you consume both during the migration, dedupe them — don't count them as two distinct collection activities. * Double emission is temporary. Once the deprecation window ends, the legacy dunning events stop firing and only `invoice.automation_schedule_step_executed` remains, migrate your consumers to the new event before then. # Not currently supported These related features and capabilities are not yet supported, but coming soon. * Programmatic API support for creating and managing automations. * Data export resources # Essential dunning Source: https://docs.withorb.com/financial-operations/essential-dunning Orb provides essential dunning capabilities for all customers help you recover revenue from failed payments and overdue invoices in an automated way to scale your collections efforts. If payment attempts is enabled and the initial attempt fails, Orb will automatically retry the charge after 1, 4, and 9 days from the initial payment attempt. If the final attempt does not succeed, Orb sends a [payment failure email notice](https://docs.withorb.com/invoicing/invoice-delivery#email-types). If the linked customer at the payment gateway does not have a payment method set, Orb will not attempt to create a dunning schedule. If payment method is updated prior to exhaustion of the dunning period, Orb will immediately try to collect on the invoice. Essential Dunning # Payments behavior * **When a payment method is present,** * If the linked customer in the payment gateway has a valid payment method on file before or at the point of an invoice entering dunning, Orb will: * Create a dunning schedule for that invoice * Immediately attempt payment collection * Updating the payment method triggers a standalone payment retry. No new dunning schedule is created * This applies even if the invoice is not yet eligible for the next retry in the existing schedule * **When no payment method is on file** * If the linked customer does **not** have a payment method when the invoice is due to begin dunning: * No dunning schedule is created * The invoice is considered ineligible for dunning # How Orb works Source: https://docs.withorb.com/how-orb-works The building blocks of usage-based billing Orb transforms customer activity into accurate invoices through a pipeline of five interconnected concepts. Understanding this flow is the foundation for building any billing model. ``` Events → Metrics → Prices → Subscriptions → Invoices ``` ## Events: Recording customer activity Everything starts with **events**—timestamped records of customer activity sent to Orb's ingestion API. ```json theme={null} { "event_name": "api_call", "timestamp": "2024-03-15T14:30:00Z", "external_customer_id": "cust_123", "properties": { "endpoint": "/v1/generate", "tokens": 1500, "model": "gpt-4", "region": "us-east-1" } } ``` Events are stored immutably in a columnar data store. They're never aggregated or mutated at ingestion time—this raw data becomes the source of truth for all billing calculations. **Why immutability matters**: Traditional billing systems increment counters as events arrive. Once incremented, the original context is lost. Orb stores raw events, so you can always re-query with different logic, backfill late data, or audit any charge down to the specific events that generated it. ## Metrics: Defining what to measure A **metric** (also called a billable metric) is a query that transforms raw events into a billable quantity. Metrics define *what* you're measuring, separate from *how* you're pricing it. | Metric type | Use case | Example | | ------------------- | ----------------------------- | ----------------------------------------- | | **COUNT** | Count events matching filters | API calls, transactions | | **SUM** | Sum a numeric property | Tokens consumed, bytes transferred | | **MAX** | Peak value during the period | Concurrent users, storage high-water mark | | **COUNT(DISTINCT)** | Unique values | Monthly active users, unique sessions | A single event stream can power multiple metrics. The same API call event might contribute to: * Total API calls (COUNT) * Total tokens (SUM of `tokens` property) * Calls by model (COUNT, grouped by `model` property) Metrics are queries, not pre-computed aggregates. Define a new metric today and calculate it against historical events—no re-ingestion required. ## Prices: Configuring rates A **price** defines how a metric quantity translates to a charge. Orb supports multiple pricing models: | Model | How it works | Example | | ----------- | ------------------------------------ | --------------------------------------------------- | | **Unit** | Fixed amount per unit | \$0.01 per API call | | **Tiered** | Rate changes at volume thresholds | First 1,000 calls free, then \$0.005 each | | **Package** | Charge per bundle of units | \$10 per 1,000 tokens (4 units used = 1,000 billed) | | **Bulk** | Volume determines rate for all units | Under 10K: \$0.02/unit; 10K+: \$0.01/unit for all | Prices also specify: * **Cadence**: Monthly, quarterly, annual, or one-time * **Timing**: In-advance (charge at period start) or in-arrears (charge at period end) * **Currency**: The billing currency or a custom pricing unit **Metric + Price separation**: The same metric can have different prices for different customers. A "tokens consumed" metric might be \$0.01/token for self-serve customers and \$0.005/token for enterprise contracts—same underlying measurement, different commercial terms. ## Plans: Packaging prices together A **plan** bundles prices into a product offering. Plans are templates that define the standard pricing for a customer segment. A plan might include: * A monthly platform fee (unit price, in-advance) * API call charges (tiered price, in-arrears) * Storage fees (unit price based on GB-hours, in-arrears) * An annual commitment minimum Plans are versioned. When you update pricing, existing subscriptions can stay on their current version or migrate to the new one—you control the timing and scope. ## Subscriptions: Connecting customers to plans A **subscription** represents a customer's ongoing relationship with a plan. It specifies: * Which customer * Which plan (or plan version) * Start date and optional end date * Any customer-specific overrides (custom rates, additional discounts, negotiated minimums) Subscriptions generate invoices according to the plan's billing cadence. A customer on a monthly plan receives an invoice each month; a customer on an annual plan with monthly charges receives monthly invoices within an annual term. **Overrides without complexity**: When an enterprise customer negotiates a 20% discount, you don't create a custom plan. Apply the discount as an override on their subscription. They're still on the standard plan—with customer-specific terms layered on top. ## Invoices: The output An **invoice** is the billing document generated for a subscription's billing period. Orb calculates invoices by querying: 1. **Events** matching the customer and billing period 2. **Metrics** defined on the subscription's prices 3. **Prices** with their rates, tiers, and adjustments 4. **Subscription terms** including overrides and discounts The result is a line-item breakdown showing exactly what the customer owes and why. ``` Invoice #INV-2024-0342 Customer: Acme Corp Period: March 1-31, 2024 Platform fee (monthly) $500.00 API calls (47,250 @ tiered) $236.25 - First 10,000 @ $0.00 $0.00 - Next 37,250 @ $0.00635 $236.25 Storage (892 GB-hours @ $0.10) $89.20 ──────────────────────────────────────── Subtotal $825.45 Discount (Enterprise 15%) -$123.82 ──────────────────────────────────────── Total $701.63 ``` Because invoices are calculated from raw events, they're fully auditable. Every line item traces to specific events and pricing rules. ## What makes this different Traditional billing systems aggregate at ingestion time. Events increment counters; counters determine charges. This is efficient but inflexible—once aggregated, the raw data is gone. Orb's query-based approach preserves raw events and calculates at billing time: | Scenario | Traditional system | Orb | | -------------------- | ------------------------------- | ------------------------------------------------- | | Late-arriving events | Lost or requires re-ingestion | Automatically included in next calculation | | Pricing change | Requires migration scripts | Apply new prices, recalculate | | Metric redefinition | Impossible without re-ingestion | Query historical events with new logic | | Audit a charge | Limited counter history | Trace to specific events | | Backdate a contract | Manual adjustments | Atomic recalculation with correct effective dates | This architectural difference is why Orb can safely backdate changes, simulate pricing scenarios, and maintain complete audit trails—capabilities that are difficult or impossible in counter-based systems. ## Next steps Detailed reference for Events, Metrics, Plans, Subscriptions, and Invoices Build your first usage-based billing workflow in 30 minutes Deep dive into immutable events and deterministic queries How safe backdating and migrations work # Adyen Source: https://docs.withorb.com/integrations-and-exports/adyen Orb supports [Adyen](https://www.adyen.com/) as a payment gateway. Once Adyen is connected and a customer is mapped to it, Orb can collect payment on invoices through the Orb invoice portal and via auto-collection, using the payment methods enabled on your Adyen merchant account. ## Setup ### Connecting Adyen with Orb Adyen connects with API credentials that you generate in your Adyen Customer Area. 1. In the Orb dashboard, navigate to **Settings > Integrations** and select **Connect to Adyen**. 2. Provide: * **API key**: an Adyen API key with the Management API "Webhooks read and write" and "API credentials read and write" roles. Orb uses these roles to provision the webhook and client key on your behalf. * **Merchant account**: the merchant account code you want Orb to transact against. * **Live URL prefix**: required when connecting in live mode (and only then). This is the prefix Adyen assigns to your live API endpoints. 3. Orb validates the credentials, then automatically: * registers a webhook from your Adyen account back to Orb (including the HMAC key used to verify it), * allowlists the Orb invoice portal as an origin, and * configures the client key the portal uses to render the payment form. You do **not** need to configure a webhook or client key manually in Adyen. Connect to adyen In **test** mode, Orb connects to your Adyen **test** environment, so you can exercise the full payment flow safely with Adyen test cards. Live mode connects to your live environment and requires the live URL prefix. Orb treats a successful authorization as payment received, so your Adyen merchant account should be configured for **automatic capture**. If your account uses manual or delayed capture, contact the Orb team before going live. ### Configuring customers When creating or updating customers in the Orb UI, select **Adyen** as the Payment Provider. Customer mappings can also be configured in the API by setting `payment_provider` to `adyen`. You do **not** need to supply a `payment_provider_id`. Adyen uses a `shopperReference` to anchor a customer's stored payment methods, but Orb manages this for you (using the Orb customer ID), so mapping a customer to `adyen` is sufficient for Orb to collect payment once the account is connected. ## Paying invoices When a customer opens an invoice in the Orb invoice portal, Orb renders an [Adyen Web Drop-in](https://docs.adyen.com/online-payments/build-your-integration/?platform=Web\&integration=Drop-in) that displays every payment method enabled on your merchant account, such as cards, ACH, PayPal, Klarna, and Bancontact. To add or remove methods, enable them on your Adyen merchant account; no changes are needed in Orb. Pay invoice ### 3D Secure authentication When a payment in the invoice portal requires 3D Secure (SCA), the Drop-in completes the challenge inline as part of the checkout, so no additional setup is needed. Off-session [auto-collection](#auto-collection) charges run without customer interaction, so a stored payment method that would require a 3D Secure challenge to authenticate cannot be charged off-session. ### Saving payment methods The Drop-in can save a customer's payment method for future use. Whether the customer is shown a "save this payment method" option is controlled by your account's payment settings: * **Always save**: the payment method is stored without prompting. * **Ask the customer**: the Drop-in renders a consent checkbox. * **Never save**: no method is stored. Saved payment methods are tokenized by Adyen and associated with the Orb customer, and become the method Orb charges for [auto-collection](/invoicing/payments#auto-collection). ## Managing payment methods via the API In addition to the portal's "save" flow, you can attach, list, and remove a customer's stored Adyen payment methods directly through the API, and choose which one is the customer's default. This is useful when you already hold an Adyen stored payment method (a `storedPaymentMethodId`) for a customer and want Orb to charge it during auto-collection. **You designate the default payment method for Adyen customers.** Tell Orb which stored method is the customer's default, either by passing `customer_default: true` when attaching it or by setting `default_payment_method_id` on the customer. Orb charges that designated default during auto-collection. A payment method object is intentionally minimal: | Field | Description | | ---------------------------- | ---------------------------------------------------------------------------------------------------- | | `id` | The Orb-assigned identifier for the payment method. | | `payment_method_type` | The underlying instrument type, one of `card`, `us_bank_account`, `link`, `amazon_pay`, or `crypto`. | | `provider_type` | The payment provider the method belongs to (e.g. `adyen`). | | `external_payment_method_id` | The identifier of this method in the payment provider (for Adyen, the `storedPaymentMethodId`). | | `customer_id` | The Orb customer the method is attached to. | | `default` | Whether this is the customer's default payment method. | | `created_at` | When the method was created in Orb. | ### Attaching a payment method Attach an Adyen stored payment method to a customer with `POST /payment_methods`. Provide exactly one of `customer_id` or `external_customer_id`, and set `customer_default` to `true` to make it the customer's default in the same call. Attaching the same `external_payment_method_id` to a customer twice returns a `409`. ```bash theme={null} curl -X POST https://api.withorb.com/v1/payment_methods \ -H "Authorization: Bearer $ORB_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "provider_type": "adyen", "external_payment_method_id": "", "customer_id": "", "customer_default": true }' ``` ### Listing payment methods List a customer's payment methods (most recently created first) with `GET /payment_methods`. One of `customer_id` or `external_customer_id` is required. The response follows Orb's [standard pagination format](/api-reference/pagination). ```bash theme={null} curl https://api.withorb.com/v1/payment_methods?customer_id= \ -H "Authorization: Bearer $ORB_TOKEN" ``` ### Removing a payment method Delete a payment method with `DELETE /payment_methods/{payment_method_id}`. This is a soft delete; if the method was the customer's default, the customer is left with no default. ```bash theme={null} curl -X DELETE https://api.withorb.com/v1/payment_methods/ \ -H "Authorization: Bearer $ORB_TOKEN" ``` ### Setting the default payment method A customer's default payment method is what Orb charges during auto-collection. The customer resource exposes it as `default_payment_method`, and you can set or clear it on `PUT /customers` with `default_payment_method_id`. Pass a payment method ID to set the default, or `null` to clear it. The payment method must belong to that customer. ```bash theme={null} curl -X PUT https://api.withorb.com/v1/customers/ \ -H "Authorization: Bearer $ORB_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "default_payment_method_id": "" }' ``` Attaching, removing, or changing the default emits the corresponding `payment_method.created`, `payment_method.deleted`, and `customer.edited` [webhooks](/integrations-and-exports/webhooks) so your systems stay in sync. ## Auto-collection When [auto-collection](/invoicing/payments#auto-collection) is enabled, Orb charges the customer's default Adyen payment method off-session on the invoice due date. Payment results are reconciled asynchronously through Adyen webhooks, and the invoice's status, timeline, and `invoice.payment_succeeded` / `invoice.payment_failed` webhooks reflect the outcome. ## Refunds Refunds issued in Adyen are reflected on the corresponding Orb invoice. To return funds to a customer, issue the refund in Adyen, then follow the [refund credit note workflow](/invoicing/credit-notes) to keep your Orb accounts receivable and balances aligned. # Best Practices for Testing Source: https://docs.withorb.com/integrations-and-exports/best-practices-for-testing # How to test Orb’s NetSuite integration Standing up a new billing system and ensuring compatibility with existing systems can be daunting. Use this guide to make implementing, testing, and going live with Orb and NetSuite a bit easier. ## Checklist ### 1. Define all stakeholders and assign a single owner upfront * The Orb NetSuite implementation typically straddles multiple teams – finance, revenue accounting, billing ops, IT/systems, and engineering. Clarify approvers for each team/function at the start. * Designate one person to own test execution end-to-end. This person should: * have Administrator access to and familiarity with NetSuite * have Admin access to Orb * be comfortable troubleshooting sync issues, creating test transactions, and reviewing sync outcomes * be responsible for consolidating feedback and questions across stakeholders. * Find the “NetSuite historian” at your company.  * Most NetSuite deployments have a long history – having easy access to that institutional memory will make a huge difference for troubleshooting collisions with existing workflows/scripts/customizations in your environment. * If your NetSuite owner is based in a non-US timezone, ensure someone else on the team has Administrator access to NetSuite to tighten the feedback loop. ### 2. Align on success criteria with accounting/finance * Before implementation starts, agree on what a successful outcome looks like from the accounting POV, not just operationally. * Define which records should be created in NetSuite, how they should be linked, and what accounting outcomes should be visible. * Examples of decisions to make upfront: * Does your business use Sales Orders or Invoices in NetSuite? * Will you bill the invoice out of Orb, or NetSuite? * Which downstream reports and reconciliations matter most to your accounting team? * Confirm who will sign off on the final accounting treatment before moving to production. ### 3. Document a scoped test plan * Define the scope of what you intend to validate, including: * Price models and billing scenarios to test * Expected record relationships in NetSuite * Expected revenue treatment and reporting outcomes in NetSuite * How you plan to operationalize invoicing (if applicable) * Share this test plan with Orb – we’ll be able to review and provide additional feedback/guidance. ### 4. Generate a small but representative set of test transactions * Start with a small set of transactions that reflect your real billing patterns. * Prefer a few high-signal examples over a large, noisy sample. Too much volume will add to reconciliation and review pain. * Use clearly labeled test customers and transactions so they are easy to identify in both Orb and NetSuite. * Be sure to test the flow both for creating new customers, and mapping to existing NetSuite customers ### 5. Test in lower environments, before promoting to production * We strongly recommend connecting Orb Test mode with your NetSuite Sandbox first, before later connecting Orb Live mode with your NetSuite production account. * Keep relevant configurations, mappings, and accounting preferences as close to parity between environments. ### 6. Queue transactions, then review results asynchronously * Orb’s NetSuite integration is asynchronous. NetSuite writes may take time to complete, so testing should not assume an immediate in-line result. * Queue a small batch of representative transactions by issuing invoices, creating credit notes, or applying payments. Allow time for the sync to complete, and return later to review the created records, links, statuses, and accounting outcomes in NetSuite. ## Common pitfalls to avoid * Collisions with other modules, workflows, scripts, and other customizations already in NetSuite can prevent the integration from behaving as expected by mutating or blocking record creation. Common examples include: * Existing Revenue Field Mappings writing to the Start and End Date target fields * Third-party tax modules suppressing tax schedule configuration * Do not bill the same customer out of Orb and another billing system at the same time. * If migrating from an existing billing vendor, de-risk the cutover using lower environments before migrating all customers to be billed on Orb. We do not recommend cohorting the rollout by customer and straddling multiple billing sources writing the same type of transaction to NetSuite. # Bill.com Source: https://docs.withorb.com/integrations-and-exports/billcom ## Prerequisites 1. Bill.com account 2. Access to the Bill.com API. * If you have not received access to the developer API, you can request access on Bill.com using [this form](https://developer.bill.com/hc/en-us/articles/360057269632-API-Signup-Request). Pending approval of this request, Bill.com should provide you with information such as your Organization ID. ## Connecting Bill.com with Orb 1. From the "Sync & Integrations" page in Bill.com, find the organization ID for your account. 2. In the Orb dashboard, click the dropdown menu on the bottom left and navigate to Organization settings, navigate to the “Integrations” tab of the modal, and select “Connect to Bill.com” 1. Enter your username (email) and password for Bill.com. 2. Enter the organization ID provided by Bill.com that you obtained in step 1. 3. Upon clicking "Save", Orb will automatically verify that your credentials are valid! 4. You should now see Bill.com as an active connection at the top of the Integrations tab. ## Mapping items Mapping Bill.com items to Orb items is optional. We recommend doing the mapping if you’d like to use any reporting or accounting automation on top of invoices in Bill.com, as it allows for standardized metadata on the invoice. In Bill.com, items can be found from the settings page under the Accounting tab. Create an item for every item that you are selling in Orb. After items are created in Bill.com, navigate to the Items tab in Orb under Settings, and select a Bill.com item for each Orb item. billcom-item ## Configuring customers When creating or updating customers in the Orb UI, select Bill.com as the Payment Provider in order to view a list of previously created customers in your Bill.com account. Customer mappings can also be configured in the API, by passing in the customer’s Bill.com ID under `payment_provider_id`, and setting `payment_provider` to `bill.com`. Find the customer’s Bill.com ID by navigating to the customer’s page in Bill.com, and copying the ID from the URL (e.g. in the URL `https://<...>.bill.com/neo/frame/customers/0cu01JHPOGEAEFIE6kue`, `0cu01JHPOGEAEFIE6kue` is the ID) Once configured with mappings, invoices for customers with a Bill.com payment provider ID will be synced to Bill.com. # Cloud marketplaces Source: https://docs.withorb.com/integrations-and-exports/cloud-marketplaces Orb's goal in the cloud marketplace ecosystem is to provide a single place to send usage events data, as well as to manage pricing and packaging evolution. This approach means that the way your operational teams manage plans and subscriptions do not have to differentiate between cloud marketplaces and native channels. Additionally, Orb provides a single place to query current usage and pricing information regardless of where your customer is processing payment. ## Pricing for marketplace subscriptions By default, setting up a cloud marketplace requires that you re-build your product catalog as a vendor on each platform (i.e. list your plan and pricing information for each dimension, such as storage or compute pricing). This approach limits your business in a few important ways: * Changing your pricing or setting up custom negotiated deals must be mediated by marketplace teams, which often manually approve terms. * It’s difficult or impossible to achieve billing automation if your pricing model is not natively supported by the marketplace platform (e.g. tiering configurations). * It can be difficult to provide a consistent experience for customers when platform functionality differs. Orb does not rely on cloud marketplaces for attaching pricing information to your usage. Instead, we recommend that you set up a single metric when configuring your offering, which represents the total number of cents that you will charge a specific customer for your service. With a single dimension in place, Orb works with a marketplace reporting partner (see below) to provide usage data to the marketplace. This data is used to generate invoices and reports for your customers. ## Reporting through partners Orb works with other vendors as first-party integrations, including [Clazar](https://help.clazar.io/articles/1021225360-orb-integration), [Suger](https://www.suger.io/docs/integrations/orb/), and [Tackle](https://docs.tackle.io/articles/#!tackle-docs/orb-integration). These integrations will natively pull usage data out of the corresponding Orb subscription and report on a regular basis to the cloud marketplace, with robust handling for all the billing features that Orb offers. We've worked with these teams to ensure proper handling of changes to the subscription lifecycle -- whether that happens through Orb or triggered by the marketplace. These specialized marketplace vendors allow you to co-sell with the marketplaces and provide dedicated marketplace-specific analytics and CPQ. These integrations can significantly speed up the time to listing on the marketplaces. Please reach out to the Orb team to understand recommendations for your specific billing setup, and currently available vendor integrations. # Overview Source: https://docs.withorb.com/integrations-and-exports/introduction Orb integrates with a number of different systems including invoicing, accounting providers and cloud marketplaces. When setting up a connection in Orb navigate to the Integrations Tab under Settings > Integrations. The page will display which integrations are enabled for your account. Please reach out to the Orb team if there is an integration you would like and is not enabled. ## Cloud marketplaces Orb allows sending usage events to different cloud marketplaces. For more information on the integrations and what is sent go [here](/integrations-and-exports/cloud-marketplaces). The three cloud marketplaces Orb has integrations with are: * AWS Marketplace * Azure Marketplace * Google Cloud Marketplace ## Payment gateways When using Orb's native Invoicing product, Orb integrates with payment gateways to collect payment on invoices — both through the Orb invoice portal and via auto-collection. Once a customer is mapped to a payment gateway (by setting `payment_provider` on the customer), Orb pulls in the relevant payment information and reflects payment status back on the invoice. Orb currently integrates with: 1. [Adyen](/integrations-and-exports/adyen) 2. [Stripe](/integrations-and-exports/stripe) If your preferred payment gateway is not on the list, please reach out! ## Accounting integrations When using the Orb Invoicing product Orb integrated with external providers to sync invoices, credit notes and payments. For more information on how to setup an accounting integration go [here](/integrations-and-exports/accounting) ## Invoicing via external integration In addition to Orb's native invoicing functionality, Orb also supports integrating with external invoicing providers. Customers of Orb use invoicing integrations to support their transition to Orb's invoices, or to maintain invoicing relationships with customers that expect invoice delivery through an existing system. Using a system for invoicing that is also your ERP (e.g. NetSuite) allows you to run reporting and other analysis with the synced data in addition to managing invoice delivery. This invoicing process syncs each line item on the invoice individually, attaches the appropriate product from the external provider, and links it to a customer in your invoicing provider instance. Invoicing via an external provider can be enabled for one or all of your customers, and different customers can be invoiced via different providers. Orb currently has integrations with: 1. [Stripe Invoicing](/integrations-and-exports/stripe) 2. [QuickBooks](/integrations-and-exports/quickbooks) 3. [Bill.com](/integrations-and-exports/billcom) 4. [NetSuite](/integrations-and-exports/netsuite) If your preferred invoicing provider is not on the list, please reach out! For each of the supported providers, Orb offers a one-way sync that exports all invoices with a a non-zero total when they're issued. After invoices are synced to the external provider, all future invoicing actions should be taken in the provider (e.g. issuing a credit note), and any updates made to invoices in the provider will not be reflected in Orb. The invoicing provider is responsible for issuing the invoice to the end customer; no Orb emails will be sent, and payment will not be collected through Orb. Please note that Orb does not export invoices with a zero total amount to reduce unnecessary noise and risk of rate limiting by external integrations. Each integration has a few specific steps, but generally the flow looks like: Authorizing the initial connection via OAuth or API keys. This can be configured from the Orb dashboard under Settings > Integrations. Mapping items in Orb to items in the external provider This ensures that invoices are linked to sellable items in your external provider, and any tax/accounting treatment is correctly applied Creating customers in Orb with the correct `payment_provider` and `payment_provider_id` set # Recognizing Credit Drawdown Source: https://docs.withorb.com/integrations-and-exports/netsuite-credit-drawdown # Overview For customers that incorporate [prepaid credits](https://docs.withorb.com/product-catalog/prepurchase#configure-prepaid-credits) in their billing, Orb’s NetSuite integration provides a turnkey solution for recognizing per-item drawdown of prepaid credits – no manual workarounds, no custom iPaaS connectors, or external consulting services needed. Get out-of-the-box accounting for: * Deferred revenue from generic credits * Item-specific recognized revenue from drawdown * Revenue recognition in the period of consumption * Recognition of breakage revenue from credit expiration Prepaid credit blocks are represented in NetSuite using NetSuite’s native Customer Deposits record type. A Customer Deposit stores a prepaid amount on a customer’s account that can be applied to future usage invoices. Each unique credit block in Orb corresponds to a unique customer deposit in NetSuite. Using a native NetSuite record type allows teams to use NetSuite’s built-in transaction relationships, application behavior, ARM workflows, reporting, and audit trail for easier reconciliation and audit-defensibility. # How it works: transaction lifecycle and integration mechanics Orb’s NetSuite integration syncs three parts of the credit lifecycle: 1. Initial credit purchase 2. Application to usage invoices 3. Credit expiration (if applicable) ## 1. Initial credit purchase For each credit block created in Orb – whether through a recurring subscription [allocation](https://docs.withorb.com/product-catalog/prepurchase#allocations) or a [manual prepaid credit purchase](https://docs.withorb.com/product-catalog/prepurchase#configure-prepaid-credits) – Orb’s NetSuite integration creates a corresponding Customer Deposit in NetSuite when the related purchase invoice is synced. The integration also creates and stages two journal entries at this phase to support the downstream accounting treatment: 1. **Prepayment clearing**: holds the Customer Deposit balance from undeposited funds to apply to a future invoice when credits are drawn down 2. **Transfer**: holds deferred revenue from the billing of a general prepaid credit item for future reclassification to item-specific revenue upon drawdown Drawdown1 1 ## 2. Application to usage invoices When prepaid credits are applied to eligible prices on a subscription invoice in Orb, the resulting drawdown is synced to NetSuite as a separate standalone sales invoice used to support item-specific revenue recognition. This invoice is distinct from the subscription invoice that Orb also syncs to NetSuite, and has no net AR impact. When the drawdown is received in NetSuite, the integration applies the corresponding Customer Deposit to mirror the credit application that occurred in Orb, marking the invoice as paid and decreasing the Customer Deposit balance. Deferred revenue is reclassified to the appropriate revenue account via journal entries generated on the next run of Update Revenue Arrangements. Drawdown2 1 ## 3. Credit expiration (if applicable) If a credit block in Orb has a defined expiration date, the integration sends a corresponding expiration event to NetSuite. This allows any remaining unapplied Customer Deposit balance associated with that credit block to be recognized as breakage revenue. Drawdown3 1 # Accounting impact (T-accounts) ## 1. Initial credit purchase Tacct1 1 ## 2. Application to usage invoices Netsuite 112 # FAQs **There is no separate cash event associated with the integration’s use of customer deposits.** Although Customer Deposits in NetSuite are commonly associated with cash transactions, in this integration they are used strictly as an accounting mechanism to hold and apply prepaid credit balances. Applying prepaid credits to usage does not create a new cash event. Instead, the Customer Deposit is used to store the prepaid balance for application against eligible invoices in NetSuite, so the credit application can be reflected using standard NetSuite transaction record types and behavior while revenue recognition continues to be handled separately through NetSuite ARM. **Each credit block in Orb maps 1:1 to a corresponding Customer Deposit in NetSuite.** If multiple credit blocks are applied to a single invoice line in Orb, the integration reflects that usage in NetSuite as multiple Customer Deposit applications applied against the same transaction line. This is common and expected, and is governed by [Orb’s credit deduction logic](https://docs.withorb.com/product-catalog/prepurchase#credit-deduction-order). **Voiding credit purchases that have already been applied to invoices cannot sync to NetSuite.** Orb disallows syncing this event to preserve the integrity of your financial data between your billing and ERP platform. See more in the core NetSuite integration mechanics guide. # Getting started: NetSuite Integration setup Source: https://docs.withorb.com/integrations-and-exports/netsuite-getting-started To setup the Orb NetSuite integration, you’ll need to: 1. **Setup and configure NetSuite.** This ensures the integration is compatible with your NetSuite configuration. 2. **Install the Orb NetSuite Integration bundle.** This deploys the custom records and scripts to enable the integration behaviors in NetSuite. 3. **Generate your NetSuite connection credentials.** You’ll use these inputs to authenticate the Orb and NetSuite connection. 4. **Create an integration user.** This is the login the integration uses to access NetSuite. 5. **Configure the integration in Orb.** This is where you define what and how Orb syncs to NetSuite. 6. **(Optional) Manually configure custom records required for credit drawdown.** If you use prepaid credits in Orb, additional setup is required to enable syncing credits and recognizing item-specific revenue in NetSuite. # Step 1: Setup and configure NetSuite You will need to do this with a user with **Administrator access** to your NetSuite account. ## Align timezones *Optional: this setting is strongly recommended, but not functionally required for the integration.* Orb and NetSuite should be configured to the same timezone to ensure transaction timestamps align correctly during sync. Mismatches can cause invoices to post to the wrong accounting period, and make debugging more difficult.  Finding your timezone settings: * NetSuite: **Setup > Company > Company Information** * Orb: **Settings > General > Account time zone** (contact Orb Support to change this) If new to Orb, set your timezone to be the same as NetSuite’s. Note that timezone changes are not retroactive — existing transaction timestamps will not be updated. If you don't have an existing preference, we recommend UTC (UTC+00:00) for both systems. ## Enable auto-generated numbers for customer creation *Optional: this setting is strongly recommended, but not functionally required for the integration.* Orb relies on NetSuite generating a unique numeric ID for each customer record to ensure syncs are idempotent. Auto-Generated Numbers must be enabled for the Customer record type in NetSuite to generate a unique numeric ID. Otherwise, NetSuite will default to the customer name as the customer ID, which can cause the sync to fail. **Setup > Company > Auto-Generated Numbers.** Check **enable** for Customer entity type. Note: this is a [NetSuite-only sequential record identifier](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/section_N252198.html), and does NOT modify any other customer identifiers you may already reference in NetSuite. It is also separate from the Orb customer ID, and is usually distinct from the external customer ID. ## Enable features Ensure the following features are enabled or selected. **Setup** > **Company** > **Enable Features**. | **Subtab** | **Feature** | **What it does** | **Required?** | | :----------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- | | Company |
  • Multiple Currencies (if billing in more than one currency) **checked**
  • File cabinet **checked**
|
  • Enables billing and transaction processing in more than one currency. Required if Orb is invoicing customers in multiple currencies.
  • Provides access to NetSuite's internal file storage.
|
  • Yes, if billing in multiple currencies
  • Yes
| | Accounting |
  • Accounting **checked**
  • Advanced Revenue Management (ARM) – Essentials **checked**
  • Rule-Based Recognition Treatment **checked**
|
  • Enables core accounting features including invoices, credit memos, and GL posting.
  • Enables revenue arrangements, recognition schedules, and deferred revenue management for Orb to handle revenue correctly.
  • Applies the correct revenue recognition rule to each transaction line based on configurable criteria. Required for multi-element arrangements with mixed recognition treatments (like usage).
|
  • Yes
  • Yes
  • Yes
| | Tax | *If calculating tax in Orb*
  • Advanced taxes **checked**
  • SuiteTax **unchecked**
|
  • Unlocks per-line tax calculation, multi-jurisdiction tax codes, and the tax infrastructure required for Orb's tax override to function correctly.
  • NetSuite's newer tax engine — incompatible with Orb's tax override implementation, and other existing tax modules.
|
  • Yes, if calculating tax in Orb
  • Yes, if calculating tax in Orb
| | Transactions |
  • Sales Orders **checked**
  • Return Authorizations **checked**
  • Advanced Shipping **checked**
|
  • Enables the sales order transaction type, which Orb uses to handle initial sale / invoice generation.
  • Enables the return authorization (RMA) transaction type, which Orb uses to handle invoice reversals and credit memo generation.
  • Enables unchecking fulfillment workflows on Items.
|
  • Yes, if using sales orders
  • Yes, if using RMAs
  • Yes
| | SuiteCloud |
  • Custom records **checked**
  • OAuth 2.0 **checked**
  • Service SuiteScript **checked**
  • REST Web Services **checked**
|
  • Enables creation and use of custom data structures in NetSuite.
  • Enables token-based authentication for external systems connecting to NetSuite.
  • Enables server-side scripts to run within NetSuite.
  • Enables NetSuite's native REST API endpoints for external integrations.
|
  • Yes
  • Yes
  • Yes
  • Yes
| ## Accounting preferences Ensure the following accounting preferences are selected. **Setup** > **Accounting** > **Accounting** **Preferences**. | **Subtab** | **Feature** | **What it does** | **Required?** | | :--------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------ | | General |
  • Require Invoice Approval When Revenue Plans Are Created On Billing **unchecked**
|
  • If checked, this approval step blocks Orb from completing the sync.
|
  • Yes
| | Order Management |
  • Default Sales Order Status = **Pending Fulfillment\***
  • Default Return Authorization Status = **Pending Receipt\***
  • Invoice in Advance of Fulfillment **checked**
  • Bill in Advance of Receipt **checked**

*\* If your NetSuite admin has configured any approval workflows, keep in mind that transactions will not be created until after the record is approved for billing.* |
  • Bypasses approval workflows that could block Orb from completing the sync.
  • Bypasses approval workflows that could block Orb from completing the sync.
  • Ensures invoice creation is not blocked on fulfillment.
  • Ensures returns are not blocked on receipt.
|
  • No
  • No
  • Yes
  • Yes
| ## Revenue Field Mapping Orb syncs line-level service period start and end dates to custom transaction line fields installed via the bundle. This ensures the values are reliably preserved during transaction creation and transformation in NetSuite. To enable proper revenue recognition set up by your administrator, map these custom fields to NetSuite’s native revenue recognition start and end date fields. **Setup** > **Accounting** > **Revenue Field Mapping** > **New**. Ensure revenue recognition field mappings have been created to reference the custom service start and end dates synced from Orb. * Source record type = Transaction Line * Automatically update target field value = T | Source field | Target field | | ---------------------- | ------------------- | | Orb Rev Rec Start Date | Start Date | | Orb Rev Rec Start Date | Forecast Start Date | | Orb Rev Rec End Date | End Date | | Orb Rev Rec End Date | Forecast End Date | If you're also syncing [fixed term subscriptions to NetSuite as a Sales Order](https://docs.withorb.com/integrations-and-exports/netsuite-how-its-synced#if-you-choose-to-sync-transactions-for-fixed-term-subscriptions-to-a-single-sales-order-with-related-sales-invoices-in-netsuite%E2%80%A6), you'll also need to configure: * Source record type = Transaction Line * Transaction type = Sales Order * Automatically update target field value = T | Source field | Target field | | :--------------------------- | :--------------------------- | | Orb Order Line Timing (Line) | Orb Order Line Timing (Line) | ## Revenue Recognition Treatments If you're also syncing [fixed term subscriptions to NetSuite as a Sales Order](https://docs.withorb.com/integrations-and-exports/netsuite-how-its-synced#if-you-choose-to-sync-transactions-for-fixed-term-subscriptions-to-a-single-sales-order-with-related-sales-invoices-in-netsuite%E2%80%A6), you'll also need to configure the following under **Setup > Accounting > Revenue Recognition Treatments**. * Name = Allocation Type Override * Description = Overrides the allocation type to "Exclude" * Allocation Type = Exclude ## Recognition Treatment Rules If you're also syncing [fixed term subscriptions to NetSuite as a Sales Order](https://docs.withorb.com/integrations-and-exports/netsuite-how-its-synced#if-you-choose-to-sync-transactions-for-fixed-term-subscriptions-to-a-single-sales-order-with-related-sales-invoices-in-netsuite%E2%80%A6), you'll also need to configure the following under **Setup > Accounting > Recognition Treatment Rules.** * Rule Name =  Orb Append Allocation Type Override * Recognition Treatment = Allocation Type Override * Criteria, Field = Orb Order Line Timing * Criteria, Value = Append ## Item configuration Orb supports syncing Non-Inventory and Service type item records. For each Item in NetSuite that corresponds to an item synced from Orb, ensure the following fields are set. **Lists > Accounting > Items**. | **Subtab** | **Field** | **What it does** | **Required?** | | :------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------- | | Primary information |
  • Includes children (**unchecked**)
|
  • Best practice to ensure specific subsidiary visibility.
|
  • No
| | Accounting | *If sales tax is calculated in Orb*.
  • Tax schedule = **Taxable**
|
  • Tells NetSuite to apply tax to this line item, which allows Orb to write the pre-calculated tax amount using the tax override code.
|
  • Yes, if calculating tax in Orb
| | Preferences | *Ensure Advanced Shipping feature is enabled\**
  • Can be Fulfilled/Received (**unchecked**)

*\*Non-inventory items only* |
  • Removes the fulfillment requirement from this line item, which can block record creation.
|
  • No, but strongly recommended
| To **bulk update item** configurations in NetSuite, use the built-in CSV import tool at Setup > Import/Export > Import CSV Records. This allows you to update fields across multiple item records simultaneously without editing them one by one. ## Tax configuration (US support only) Syncing tax is only supported for transactions in US-jurisdictions. International support is coming soon. If you bill and collect tax on invoices in Orb, configure NetSuite to receive tax amounts for accurate reporting in NetSuite. **Our approach** Orb calculates tax upstream and passes pre-calculated tax information to NetSuite, rather than relying on NetSuite's tax engine be responsible. To do this reliably, we use a two-step process: 1. **Record creation** — Orb creates the transaction with `taxrate1` set to 0, establishing the tax placeholder on the record 2. **Tax application** — Orb performs a subsequent edit to the record, overwriting the correct Orb-calculated tax amounts per line This two-step approach ensures that Orb's tax values are applied precisely and are not overwritten by NetSuite's internal recalculation engine — giving you a clean, authoritative record of exactly what was calculated and billed in Orb. **Why we use** `taxrate `**instead of an amount field** In NetSuite, `record.setSublistValue` truncates `tax1amt` to an integer when given a non-integer value. Since invoicing precision tax values are the norm, Orb takes the approach of deriving the final tax amount in NetSuite using `taxrate1` instead of writing to the `tax1amt `field directly. In these cases, this is why you might see a modified `taxrate1` value sent from Orb during record creation. For sales order flows, we intentionally suppress estimated tax on the sales order and ensure the final invoice carries the correct tax/GL impact with no revenue impact or duplication.  **Step 1: Enable per-line taxes on transactions (US jurisdiction)** The “per-line taxes on transactions” setting tells NetSuite to calculate and display tax at each transaction line instead of only at the header/body level. For Orb, this is required so our tax override can populate line-level tax behavior correctly rather than collapsing tax to the transaction total. To enable it, **Setup > Accounting > Tax Preferences**, open United States and check the “per-line taxes on transactions” option. If you don’t see this field, ensure Advanced Taxes is enabled. **Step 2: Create an Orb tax override code (US jurisdiction)** Orb uses this code together with NetSuite’s line-level `tax1rate`behavior so that NetSuite respects the tax information Orb sends, instead of recalculating tax based on its own rates. 1. Create a new tax code for Orb under **Setup** > **Accounting** > **Taxes** > **Tax Codes** > **New**. 1. Name it “Orb Tax Override”, (or similar). 2. Set the tax rate to 0%. 3. Choose the appropriate tax type and tax agency for your environment. 4. Ensure it is in the correct subsidiary. 2. We strongly do NOT recommend configuring any other optional fields in this override code – this adds additional validation logic for NetSuite’s tax engine, that pollutes the passthrough functionality. 3. **Save**. # Step 2: Install the Orb NetSuite Integration bundle 1. Navigate to **Customization** > **SuiteBundler** > **Search & Install Bundles**. 2. Search **Orb NetSuite Integration Bundle**. ID: `597717`. (Ask Orb to approve bundle access before installation.) 3. Select the bundle from the results and click **Install**. 4. Wait for the installation to complete. This may take a few minutes. The bundle will install SuiteScripts and custom objects (fields, records, roles, etc) into your NetSuite account. ## **What's in the bundle** * 14 transaction line custom fields + 6 body fields + 1 entity field * 1 custom error record type * Orb Integration Role (includes \~50 required permissions for minimum integration functionality) * RESTlet + bundle installation script These components are locked – you cannot edit/view field definitions. | Custom Fields | Script ID | Purpose | | --------------------------- | ------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Entity fields** | | | | Orb customer ID | `custentity_orb_external_id` | Represents the Orb external ID for the customer that triggered the creation of this NetSuite customer. | | **Transaction body fields** | | | | Orb invoice number | `custbody_orb_invoice_number` | Represents the Orb invoice number that triggered the creation of this NetSuite record. | | Orb credit note number | `custbody_orb_creditnote_number` | Represents the Orb credit note number that triggered the creation of this NetSuite record. | | Orb credit block ID | `custbody_orb_credit_block_id` | Represents the credit block ID that triggered the creation of this NetSuite record. Only applicable for recognizing prepaid credit drawdown. | | Orb originating invoice | `custbody_orb_originating_inv` | Represents the purchase invoice tied to the creation of this NetSuite record. Only applicable for recognizing prepaid credit drawdown. | | Orb prepaid credit type | `custbody_orb_prepaid_credit_type` | Represents whether this record corresponds to an Allocation or Manual credit block in Orb. Only applicable for recognizing prepaid credit drawdown. | | Orb subscription ID | `custbody_orb_subscription_id` | Represents the Orb subscription that triggered the creation of this NetSuite record. Only applicable for Subscription-driven sales order mapping configurations. | | **Transaction line fields** | | | | Orb Rev Rec Start Date | `custcol_orb_rev_rec_start_date` | Represents the line item service period start date. Mapped to the revenue element start date via revenue field mapping. | | Orb Rev Rec End Date | `custcol_orb_rev_rec_end_date` | Represents the line item service period end date. Mapped to the revenue element end date via revenue field mapping. | | Orb List Rate | `custcol_orb_list_rate` | Represents unadjusted subtotal amount before adjustments have been applied. | | Orb Discount Percentage | `custcol_orb_discount_percentage` | Represents the percentage discount adjustment configured in Orb. | | Orb Discount Amount | `custcol_orb_discount_amount` | Represents the amount discount adjustment configured in Orb. | | Orb Discounted Rate | `custcol_orb_discounted_rate` | This is a derived value calculated outside of Orb. Represents the per-unit adjusted rate. | | Orb Discounted Amount | `custcol_orb_discounted_amount` | This is a derived value calculated outside of Orb. Represents the per-line adjusted subtotal. | | Orb Coupon Applied | `custcol_orb_coupon_applied` | Represents the coupon amount discount adjustment configured in Orb. | | Orb Maximum Adjustment | `custcol_orb_max_adjust` | Represents the subtotal delta above the maximum adjustment configured in Orb. | | Orb Minimum Adjustment | `custcol_orb_min_adjust` | Represents the minimum adjustment configured in Orb. | | Orb Credit Block ID | `custcol_orb_credit_block_id` | Represents the credit block ID that triggered the creation of this NetSuite customer deposit. Only applicable for recognizing prepaid credit drawdown. | | Orb Prepaid Credit Type | `custcol_orb_prepaid_credit_type` | Represents whether this record corresponds to an allocation or manual purchase credit block in Orb. Only applicable for recognizing prepaid credit drawdown. | | Orb Credit Expiration Date | `custcol_orb_credit_expiration_date` | Represents the expiration date configured on the corresponding credit block in Orb, if applicable. Only applicable for recognizing prepaid credit drawdown. | | Orb Line Item ID | `custcol_orb_line_item_id` | Represents the corresponding Orb line item ID to help with debugging. | # Step 3: **Generate your NetSuite connection credentials** You will need to do this with a user with **Administrator access** to your NetSuite account. ### **1. Verify the script deployment from the bundle** 1. Navigate to **Customization** > **Scripting** > **Script Deployments**. 2. Find the RESTlet deployment installed by the bundle and click on ‘View’. Netsuite5 3. Verify the deployment **Status** is **Released**. 4. Click on the name of the script, then note the **URL** — you will need it for the installation step. Netsuite6 ### 2. Create an integration record The integration record identifies the external application that will connect to your NetSuite account. 1. Navigate to **Setup** > **Integration** > **Manage Integrations** > **New**. 2. Enter a **Name** for the integration (e.g. “Orb NetSuite Integration”). 3. Under the **Token-based Authentication** section: * Uncheck **TBA: Authorization Flow** * Uncheck **Token-Based Authentication** 4. Under the **OAuth 2.0** section: * Uncheck **Authorization Code Grant** * Check **Client Credentials (Machine to Machine) Grant** 5. Under **Scope**, check the following: * **RESTlets** * **REST Web Services** 6. Click **Save**. Netsuite 7 7. After saving, NetSuite displays the **Consumer Key / Client ID** under the **Client Credentials** section. Copy this value and store it securely — it is shown only once and cannot be retrieved later. Netsuite8 ### **3. Generate a certificate key pair** In this step you’ll use your computer’s terminal to create two files: a private key and a self-signed certificate. This is a one-time operation — if anything goes wrong, you can delete the files and start over. Before you begin, you need **OpenSSL** available in your terminal. macOS and most Linux distributions include it by default. On **Windows**, use **Git Bash** (bundled with [**Git for Windows**](https://gitforwindows.org/), which includes OpenSSL), **WSL**, or install OpenSSL and ensure it is on your `PATH`. **1. Create a folder and open a terminal in it** Create a new folder anywhere you’ll remember — your Desktop works fine. For example, create one called `netsuite-keys`. Then open a terminal *inside that folder* so the files you generate end up there: * **macOS**: In **Finder**, right-click the folder and choose **New Terminal at Folder**. (If you don’t see this option, enable it under **System Settings** > **Keyboard** > **Keyboard Shortcuts** > **Services** > **Files and Folders** > **New Terminal at Folder**.) * **Windows**: In **File Explorer**, right-click inside the folder and choose **Open in Terminal** or **Open Git Bash here**. * **Any OS (fallback)**: Open your terminal app, type `cd `(with a trailing space), drag the folder from your file browser onto the terminal window, and press **Enter**. **2. Run the OpenSSL commands** Copy each command below, paste it into the terminal right after the prompt (`%` on macOS, `$` on Linux/Git Bash, `PS>` on PowerShell), and press **Enter**. Run them one at a time. ```text theme={null} openssl ecparam -name prime256v1 -genkey -noout -out private-key.pem ``` ```text theme={null} openssl req -new -x509 -key private-key.pem -out cert.pem -days 730 ``` The second command will prompt you for certificate details (country, organization name, email, etc.). **Press Enter at every prompt to accept the defaults** — these values are not used by the integration, so it doesn’t matter what they are. **3. Confirm the two files were created** If the commands succeeded, your folder now contains two files: * `private-key.pem` — Your private key. You will base64-encode this and share it when installing the integration. * `cert.pem` — Your public certificate. You will upload this to NetSuite. Open the folder in **Finder** (macOS) or **File Explorer** (Windows) to confirm both files are there. If you can’t see them, search your computer for `cert.pem` — the files were created in whichever folder your terminal was open in. If anything looks wrong — missing files, error messages, accidentally hit Enter too many times — just delete the files and run the commands again. The keys aren’t registered anywhere until you upload `cert.pem` to NetSuite, so there’s no risk in starting over. **4. Base64-encode the private key** Run the command below to print a base64-encoded version of your private key directly in the terminal. Use the version that matches your environment: **macOS (Terminal)** ```shellscript theme={null} base64 -i private-key.pem ``` **Linux, Git Bash, or WSL** ```shellscript theme={null} base64 -w 0 private-key.pem ``` **Windows (PowerShell)** From the directory that contains `private-key.pem`: ```powershell theme={null} [Convert]::ToBase64String([IO.File]::ReadAllBytes((Resolve-Path 'private-key.pem'))) ``` Copy the encoded string (a long single line, e.g. starting with `LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0t...`). It will be required during the installation process. # Step 4: Create an integration user and authorize a M2M connection **Create an integration user** We strongly recommend creating a dedicated NetSuite integration user for the Orb integration, rather than connecting with a personal administrator account. This keeps access predictable over time, and makes it easier to audit which changes are driven by the integration. 1. **Create a new NetSuite user** intended only for the Orb integration (for example, [orb-integration@yourcompany.com](mailto:orb-integration@yourcompany.com)). 2. **Assign an appropriate role** to this user: 1. For fastest setup, you can assign the pre-defined Orb Integration Role that ships with the Orb NetSuite Integration bundle (recommended). 1. Navigate to **Setup** > **Users/Roles** > **User Management** > **Employees** (or Lists > Employees). 2. Find and edit the employee record you want the integration to run as. This can be an existing user or a dedicated integration user. 3. Go to the **Access** tab. 4. In the Roles subtab, click **Add**. 5. Select the custom role from the dropdown. 6. Click **Save**. 2. If your security team requires stricter scoping, you can instead create a custom role based on NetSuite’s permissions model, using our Required permissions section as a baseline. Use this user to authorize the Orb → NetSuite connection during integration setup. Any future permission changes can then be made by editing this role, without touching individual employee accounts. **Create a machine-to-machine certificate mapping** 1. Navigate to **Setup** > **Integration** > **OAuth 2.0 Client Credentials (M2M) Setup**. 2. Click **Create New.** 3. Configure the following fields: 1. **Entity**: Select the integration user (from the previous step) 2. **Role**: Select the custom role that the bundle installed. Please note that the role must include the **Log in Using OAuth 2.0 Access Tokens** permission. 3. **Application**: Select the integration record you created. 4. **Certificate**: Upload the `cert.pem` file you generated. 4. Click **Save**. Netsuite9 5. Copy the **Certificate ID** from the list — you will need this value when installing the integration. Netsuite10 Check that the `Valid Until` date for the certificate is two years in the future. NetSuite allows a maximum validity of 730 days. # Step 5: Configure the integration in Orb Once your NetSuite bundle is installed and the dedicated integration user is ready, you’ll complete the connection from within Orb. This step links your Orb account to your NetSuite environment using that integration user and enables the asynchronous, one‑way sync from Orb → NetSuite. ## 1. Navigate to the NetSuite integration in Orb. 1. Go to [Settings > Integrations > Accounting](https://app.withorb.com/settings?tab=integrations). ## 2. Start the connection flow. 1. Click **Connect**. 2. When installing the integration, you will be prompted for a number of inputs: Netsuite11 Here is where to find each value: | **Input** | **Where to find it** | | :------------------------- | :---------------------------------------------------------------------------------------------------------------------------- | | **Client ID** | The Consumer Key saved. | | **Certificate ID** | The Certificate ID saved. | | **Private Key** | Base64-encoded contents of `private-key.pem`. | | **Account ID** | Your NetSuite URL (e.g. `https://1234567.app.netsuite.com` → Account ID is `1234567`). | | **RESTlet Deployment URL** | This is printed out in the Deployments tab of the script, and looks like `/app/site/hosting/restlet.nl?script=3045&deploy=1`. | **Sandbox accounts**: If you are connecting a sandbox account, your Account ID may contain an underscore and suffix (e.g. `1234567_SB1`). Provide it exactly as shown in your NetSuite URL or Company Information page. 3. Select and accept all permissions. ## 3. Configure sync behavior and mappings. 1. After establishing a successful connection, define how Orb maps to your NetSuite account. 1. **Subsidiary** → select which entity transactions are created in. 2. **Location** → select a value if this segment is required on a NetSuite transaction. Else, the record will fail to sync. 2. Select the **tax override** **code** configured above, if syncing taxes applied to invoices issued from Orb. 3. Select a **bank account** per currency to determine where payments received in Orb should be recorded. 4. Define the scope of transactions synced from Orb, using controls like minimum invoice date or amount. 5. Configure how NetSuite handles transactions synced from Orb – enable to create sales orders and RMAs. 6. **Map items**. 1. A NetSuite Item controls how a line item behaves financially, including which GL account it impacts, how revenue is recognized, and how it’s handled operationally. This mapping determines how Orb line items behave when synced to NetSuite. 2. The integration supports NetSuite items of type = Non-Inventory For Sale. 7. **Map customer metadata.** 1. Any customer-level metadata created before a customer is synced from Orb to NetSuite can be mapped and synced to NetSuite custom entity fields upon creation.  2. This lets you keep key attributes (for example, account owner, billing segment, go‑live date, or internal IDs) aligned between systems without manual re-entry. 3. This is a one-time initial sync upon customer creation in NetSuite.  1. Subsequent updates to this field in Orb are not synced to NetSuite. 2. New customer-level metadata added to the Orb customer record does not update in NetSuite. 8. Review the configuration summary, then save. The review step summarizes the NetSuite setup, accounting settings, chart of accounts, and Orb-to-NetSuite mappings from the previous steps. Any unmapped item will fail to sync to NetSuite. # Step 6: (Optional) Manually configure custom records required for credit drawdown. In order for Orb’s integration to recognize prepaid credit drawdown in NetSuite, the following GL accounts and items must be manually created and configured in NetSuite. ## Custom GL accounts Setup > Accounting > Chart of Accounts > New. | **Account Name** | **Account Type** | **Purpose** | | ------------------------------------------ | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------- | | Deferred Revenue - Prepaid Credits | Deferred Revenue | Tracks the total prepaid credit balance available for usage, reduced as credits are consumed and cleared when credits expire. | | Prepayment Clearing - Prepaid Credits | Other Current Liability | Used to enable Customer Deposit application. Always offsets Customer Deposits liability and collapses to zero at expiration. | | Deferred Revenue - Usage Clearing | Deferred Revenue | Temporary account that serves as an intermediary for journal entries, always netting to zero after each transaction. | | Revenue - Expired Prepaid Credits | Income | Breakage/expiration revenue. Credited by ARM at month-end close when credit expiration is recognized. | | Deferred Revenue - Expired Prepaid Credits | Deferred Revenue | Holds the balance of expired prepaid credits for recognition. | **Validate your Customer Deposits GL account** Relatedly, while not a custom GL account you need to configure, a Customer Deposits GL account is required for the credit drawdown behavior to function as expected. To check: 1. Navigate to **Setup → Accounting → Chart of Accounts** 2. Confirm that an account with **Name** = Customer Deposits and **Type** = Other Current Liability exists. If it does, no further action is needed. To create one, if needed: 1. Navigate to **Transactions → Customers → Record Customer Deposit** 2. Populate the following fields: * **Customer** — select any active customer, preferably a test customer * **Payment Amount** — enter a nominal amount (e.g. \$0.01) * Any additional mandatory fields required by your NetSuite configuration 3. Click **Save** 4. Navigate back to **Setup → Accounting → Chart of Accounts** and confirm the Customer Deposits (Other Current Liability) account now appears. 5. Once confirmed, navigate back to the test Customer Deposit record and delete it. The GL impact will be automatically reversed upon deletion. ## Custom revenue recognition rule Lists > Accounting > Revenue Recognition Rule > New. | Rule | Requirements | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Custom Rev Rec Rule |
  • Name: Credit Expiration
  • Recognition Method: Straight-line, by even periods
  • Amount Source: Event-Percent based on amount
  • Rec Rec Start Date Source: Event Date
  • Rev Rec End Date Source: Event Date
  • End Date Change Impact: Update Remaining Periods Only
| ## Custom recognition event type Lists > Accounting > Custom Recognition Event Types > New. | Object | Requirements | | ----------------------------- | ------------------------------------------------------------------------------------------------------- | | Custom Recognition Event Type |
  • Name: Prepaid Credit Expiration
  • Create Recognition Plan Per Event **checked**
| ## Custom payment method Setup > Accounting > Accounting Lists > Payment Method > New. | Object | Requirements | | --------------------- | --------------------------------------------------------------------------------------------------------------- | | Custom Payment Method |
  • Name: Prepaid Credits
  • Type = Offline
  • Group with Undeposited Funds **checked**
| ## Items Lists > Accounting > Items > New. | Item Name | Requirements | | | ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- | | Prepaid Credit |
  • Item type: Service for Sale
  • Item type: Service for Sale
*Note: you’ll need to configure a credit item per credit currency / credit type. (Ex: USD credits vs. CR credits)* | | | Prepaid Credit Expiration |
  • Item type: Service for Sale
|
  • Item type: Service for Sale
| | Allocation Expiration | Same as "Prepaid Credit Expiration". | | # How It's Synced Source: https://docs.withorb.com/integrations-and-exports/netsuite-how-its-synced When syncing transactions from Orb to open accounting periods in NetSuite, the integration supports creating sales invoices and credit memos, OR sales orders and return authorizations. Choose the transaction mapping configuration that best fits your finance + accounting team’s preferences, and existing business processes. For each configuration selected, the following transaction steps will occur (creation, transformation, and mutations.) # Syncing fixed-term subscriptions Subscriptions with a fixed-term (defined end date) represent canonical customer “contracts” in Orb. ### **If you choose to sync transactions for fixed-term subscriptions as standalone Sales Invoices in NetSuite…** Nsmap1 Nsinvoiceonly ### **If you choose to sync transactions for fixed-term subscriptions to a single Sales Order with related Sales Invoices in NetSuite…** Nsmap2 Nscomplexso For fixed-term subscriptions synced as sales orders, Orb treats the sales order as the contract-level record rather than just a reflection of a single invoice cycle. Orb aligns the sales order to the subscription term defined in Orb — specifically, the subscription’s end date — so NetSuite can represent the full scope of the commitment and apply revenue recognition over the intended contract term, instead of being constrained by invoicing cadence alone. Mechanically, Orb does this by pre-computing termed fixed-fee line amounts on the sales order. For each eligible fixed-fee line, Orb takes the amount configured on the Orb subscription and scales it by the number of billing terms in the subscription, so the sales order reflects the full termed value of the contract rather than only the amount billed on the current invoice. Netsuite111 # Syncing evergreen subscriptions Subscriptions that have no defined end date represent recurring agreements in Orb. ### **If you choose to sync transactions for evergreen subscriptions as standalone Sales Invoices in NetSuite…** Nsmap3 Nsinvoiceonly ### **If you choose to sync transactions for evergreen subscriptions as standalone Sales Orders in NetSuite…** Nsmap4 Nssimpleso # Syncing one-off invoices [One-off invoices](https://docs.withorb.com/invoicing/one-off-invoice-creation#one-off-invoices) represent ad hoc charges outside the subscription. ### **If you choose to sync one-off invoices as standalone Sales Invoices in NetSuite…** Nsmap5 Nsinvoiceonly ### **If you choose to sync one-off invoices as standalone Sales Orders in NetSuite…** Nsmap6 Nssimpleso In sales-order-based mappings, Orb creates a full Return Authorization (RMA) in NetSuite and then transforms that RMA into a credit memo applied to the related sales invoice, rather than relying on NetSuite’s native void mutation. NetSuite’s native void behavior is unreliable in practice – it is highly sensitive to environment-level configurations and accounting preferences such that native voids may not be consistently available or appropriate for all transaction types. More importantly, for sales order setups, a pure NetSuite sales invoice void leaves the related revenue arrangement on the sales order live, whereas an RMA created from the sales order reverses the revenue elements and triggers the corresponding revenue plan reversals. This makes the RMA flow the closest NetSuite-native equivalent of the full AR and revenue reversal that occurred in Orb. Many NetSuite finance teams prefer explicit reversal records over native invoice voids for corrections, because those approaches create a clearer accounting trail and align better with standard controls. # How subscription edits are handled in NetSuite [Subscription edits](https://docs.withorb.com/product-catalog/editing-subscriptions#subscription-edits) and [plan changes](https://docs.withorb.com/product-catalog/subscription-plan-change#subscription-plan-changes) are reflected in NetSuite when finalized billing events are synced from Orb. This keeps NetSuite aligned to stable, auditable billing records rather than in-progress commercial state. At a high level, Orb handles subscription edits using two principles: * **Forward-looking changes** that only affect unbilled periods have no immediate effect in NetSuite until the upcoming finalization event.  * The exception is fixed-term subscriptions that see an immediate adjustment to the sales order in NetSuite. * **Backdated changes** that affect already-issued invoices are reflected through Orb’s normal credit and reversal flows. Here are the specific impacts for different types of subscription edits, for different types of subscriptions in Orb: | **Amendment Type** | **Fixed-term subscriptions** | **Evergreen subscriptions & one-off invoices** | | :---------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- | | Forward-looking rate changes for fixed fee prices | A partial-line RMA closes out the original line on the Sales Order. A new line is created for the remaining subscription term at the updated rate. | Reflected on the next Sales Order or Invoice. | | Forward-looking increase / decrease in (fixed fee) quantity | A partial-line RMA closes out the original line on the Sales Order. A new line is created for the remaining term at the updated quantity | Reflected on the next Sales Order or Invoice. | | Forward-looking change to usage price, rate or quantity | Usage lines are always additive — changes are appended as new lines on the Sales Order. No RMA required. | Reflected on the next Sales Order or Invoice. | | Forward-looking (scheduled) cancellation | A partial-line RMA closes out the remaining subscription term. | Reflected on the next Sales Order or Invoice. | | Forward-looking Term (end date) changes | Sales Order end date is updated. A partial-line RMA closes out affected fixed fee lines, and new lines are created for the extended term. | Reflected on the next Sales Order or Invoice. | | Backdated changes | Handled via RMA, credit memo, or record void reversing the affected period and reposting the corrected amounts. | Handled via RMA, credit memo, or record void reversing the affected period and reposting the corrected amounts. | # Overview Source: https://docs.withorb.com/integrations-and-exports/netsuite-overview # Introduction Orb’s NetSuite integration connects Orb’s billing and revenue data to your NetSuite environment to trigger the creation of native transaction records in NetSuite. This allows you to leverage NetSuite’s standard accounting objects, fields, and workflows for revenue recognition, general ledger updates, and downstream reporting. Orb remains the source of truth for billing, while automatically pushing the necessary information to NetSuite without manual re-entry or reliance on custom scripting. The result is a complete, accurate, and auditable system of record. # Key components At a high level, the integration experience spans four main components: 1. **Orb (billing provider)** This is the source of your transaction data. 2. **The Integration** This is how data “syncs” from Orb to NetSuite. It is a one-way push via REST APIs, is configured in Orb, and requires a dedicated NetSuite integration user for authentication. 3. **Orb NetSuite Integration Bundle** This is a packaged set of customizations – fields, scripts, configurations – that will be installed into your NetSuite account to enable integration functionality. 4. **NetSuite (ERP)** This is the destination of your transaction data. Your general ledger / chart of accounts, item records, revenue configurations are here. **This integration requires NetSuite's Advanced Revenue Management (ARM) module, Essentials module.** ARM Essentials specifically is what enables automated revenue arrangements, period-level recognition schedules, and accurate deferred revenue tracking in NetSuite. ARM Advanced (Revenue Allocation) is not required. Netsuite 4 # Integration scope Orb’s NetSuite integration supports the following: 1. New Customer creation, existing customer mapping 2. Customer metadata sync 3. Item mapping 4. Transaction record creation and transformation 5. Revenue arrangement creation 6. Payments sync and application 7. Credit drawdown with item-specific recognition and reporting 8. Breakage revenue # Integration behavior ### Orb’s NetSuite integration is designed as an **asynchronous**, queue-based experience. * NetSuite is not designed for synchronous, high-throughput writes. NetSuite enforces rate limits, variable response times, and occasional transient failures. * Async processing allows us to queue, retry, and reconcile transactions. This better guarantees delivery, prevents data loss, and enables idempotency and safe retries. ### Orb records are synced **post-issuance** for financial integrity * This is by design, especially in a usage-context where the underlying data is continuously changing up until the point of invoice issuance. * Pushing in-progress or “real-time” data into NetSuite introduces significant risk that requires reversals and corrections, leading to inconsistencies and unnecessary operational overhead. * By syncing only finalized transactions, Orb ensures that NetSuite reflects stable, accurate financial records—eliminating the need for reversals and enabling a clean, scalable integration model. ### **Sync timing** * *In a stable state*, new or updated transaction records should **sync to NetSuite within \~10 minutes**, and no more than 30 mins under normal operating conditions.  * Actual timing can fluctuate based on NetSuite API availability, your account’s performance (based on concurrency limits and your service tier), or temporary maintenance outages. ### This is a passthrough integration enabled by a NetSuite **RESTlet** built on REST APIs and SuiteQL. * A RESTlet is a server‑side script (deployed as part of the bundle) that exposes custom endpoints into your NetSuite account needed to enable integration behaviors. * We power Orb “writes” to NetSuite (for creating transactions and records, transforms and mutations etc.) using REST APIs. * REST APIs provide a more modern, efficient, and scalable way to interact with NetSuite. They support structured JSON payloads, improved performance, and more predictable behavior. * This aligns with NetSuite’s ongoing shift away from legacy SOAP-based integrations. As NetSuite continues to invest in REST as its primary integration framework, this approach ensures long-term compatibility and avoids reliance on deprecated or legacy interfaces. * We power Orb “reads” from NetSuite (for returning configuration values like customer, subsidiary and bank account etc.) using SuiteQL. * This is NetSuite’s native SQL‑like query language that lets Orb run parameterized queries over NetSuite records via the same modern REST channel. ### It’s designed as a one-way sync from Orb to NetSuite * This approach is grounded in maintaining strong data integrity across your systems, and preventing data contamination from conflicting updates, data drift, or missing transactions. * To preserve a consistent and auditable source of truth, data should always move downstream—from CRM → Orb → NetSuite—rather than being written back upstream or modified across systems. The following behaviors described represent the standard supported NetSuite integration functionality that Orb has designed, implemented, and thoroughly tested across multiple environments. They assume: 1. The Orb NetSuite Integration bundle is installed as documented. 2. Bundle records, fields, and scripts are un-edited – the only changes are due to bundle upgrades published and recommended by Orb. 3. No custom scripts, workflows, or third‑party bundles are modifying the same transactions in conflicting ways. 4. Required NetSuite features and preferences are enabled. If you see outcomes that differ from what is documented here, most commonly: * There is a NetSuite-side customization (workflow, script, plugin, or bundle) mutating records after Orb writes them, or * A required NetSuite configuration is missing or conflicting. When behavior diverges from this guide, we recommend first reviewing your NetSuite customizations and configuration for collisions (for example, additional tax logic, revenue scripts, approval workflows, or field defaulting rules). If after that, the behavior still appears inconsistent with this document, please contact Orb Support with concrete examples so we can help investigate. # Sync Reconciliation And Troubleshooting Source: https://docs.withorb.com/integrations-and-exports/netsuite-sync-reconciliation-and-troubleshooting # Manual reconciliation of unsupported sync actions Orb’s NetSuite integration is designed to preserve the integrity of your financial data between your billing and ERP platform. For a handful of cases where sync handling requires additional intervention or accounting judgment, the integration intentionally does not perform an automated sync. Instead, those cases are surfaced as “Action Needed” errors for manual reconciliation in NetSuite. These unsupported cases are: 1. Posting transactions into closed accounting periods in NetSuite 2. Creating invoices for which a customer balance has been applied in Orb 3. Voiding credit purchases that have already been applied to invoices | Unsupported sync action | Collision | Recommended remediation | | ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
  1. Posting transactions into closed accounting periods in NetSuite
| In **Orb**, [accounting period locks ](https://docs.withorb.com/revenue-reporting/revenue-recognition#actions-in-the-past-and-accounting-period-locks)govern where AR and revenue are reported, but don’t disallow invoicing actions.
In **NetSuite**, locking accounting periods disallow all transaction activity dated in a closed period. | To sync a transaction dated for a closed period, do one of the following:
  1. Open the relevant accounting period in NetSuite, then resync the invoice from Orb.
  2. OR, manually create a journal entry in NetSuite, then mark the invoice as synced in Orb.
  3. OR, do nothing, and cancel the sync.
| |
  1. Creating invoices for which a customer balance has been applied in Orb
| In **Orb**, any available [customer balance](https://docs.withorb.com/invoicing/customer-balance-transactions#customer-balance) is automatically applied to a subsequent invoice and modifies the final amount due. Customer balance can be created from refund credit notes, or manual adjustments.
In **NetSuite**, the equivalent of customer balance is represented as an AR liability from an open but unapplied credit memo. It must be manually applied to an eligible invoice. | To sync an invoice with customer balance applied:
  1. Sync the invoice anyways, without the balance applied.
  2. Manually apply the credit memo that created this customer balance in NetSuite, then mark this invoice as synced.
  3. OR, do nothing, and cancel this sync.
| |
  1. Voiding credit purchases that have already been applied to invoices
| In **Orb**, once a credit block has been applied to an issued or paid invoice, it can not be returned to the ledger. This holds even if the original credit block has been voided or modified. | To sync a void on a credit block that has already been applied to invoices:
  1. OR, do nothing, and cancel this sync.
| # Idempotency handling, record retries and resyncs ## Idempotency handling Orb’s NetSuite integration is designed as an asynchronous, queue‑based system that prioritizes idempotent writes: running the same sync job more than once should converge to the same NetSuite state, rather than creating duplicate or conflicting records. 1. Orb syncs finalized transactions (invoices, credit notes, payments) into NetSuite and records each attempt as an accounting sync record with its own lifecycle and status. 2. For each record type, Orb uses stable identifiers (for example, Orb customer ID, Orb invoice number, Orb credit note number) mapped into locked custom fields in NetSuite so repeated writes can be recognized and reconciled safely. 3. The integration sync view in Orb surfaces the full timeline of attempts per record, including queued, in‑progress, synced, and action‑needed states. ## Record retries Orb automatically retries certain types of failures to keep NetSuite in sync without manual intervention: * **Transient errors**: Network issues, NetSuite rate limits, and other transient 4xx/5xx responses are treated as retryable. Orb keeps the record in the NetSuite queue and re‑attempts the write according to internal backoff rules. * **Non‑retryable errors**: Structural problems such as missing mappings, invalid configuration, or NetSuite validation failures are surfaced as Action needed in the sync view so an operator can address the root cause. Once an error is visible in the sync view, you can: * Use **Retry record for an individual failure** to attempt the same payload again after fixing the issue (ex: incorrect item mappings). * **Retry all failed** to sync all failed records. ## Sync errors and remediation Here’s a list of supported integration errors, and recommended troubleshooting or remediation steps. ### Period / State Errors | Sync error | What this means | Recommended remediation | | :------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------- | | Missing accounting configuration | The NetSuite integration is not fully configured in Orb, so Orb does not have the settings it needs to sync the record. | Review the NetSuite integration settings in Orb and complete any missing accounting configuration before retrying the sync. | | Missing NetSuite subsidiary ID | Orb could not find a required subsidiary value for the sync, so it cannot create the NetSuite record in the correct entity context. | Configure the NetSuite subsidiary in the integration settings and then retry the failed record. | | Unable to determine subsidiary | Orb could not determine which NetSuite subsidiary should be used for this record based on the current configuration and record data. | Check the integration's subsidiary selection logic and confirm the customer, transaction, and mapping data point to a valid subsidiary. | | Insufficient permissions | The NetSuite role used by the integration does not have permission to perform the requested operation. | Update the NetSuite integration role to include the required permissions, then retry the sync. | ### Customer Errors | **Sync error** | **What this means** | **Recommended remediation** | | :------------------------ | :----------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------ | | Duplicate customer name | NetSuite found an existing customer with the same name, so Orb cannot safely create a new customer record automatically. | Map the Orb customer to the correct existing NetSuite customer using an external customer ID, or rename the conflicting customer record if appropriate. | | Customer record not found | The NetSuite customer Orb expected to use for the sync does not exist or is no longer accessible. | Verify the customer mapping in Orb, confirm the NetSuite customer still exists, and reconnect the record to a valid customer before retrying. | ### Reference / Record Errors | **Sync error** | **What this means** | **Recommended remediation** | | :---------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------- | | Invalid or missing reference record | A referenced NetSuite record such as a subsidiary, item, entity, or other mapped object is missing, invalid, or cannot be used for this operation. | Check the referenced NetSuite records and mappings, restore or replace any invalid records, and then retry the sync. | | Missing item mappings | One or more Orb items in the transaction are not mapped to NetSuite items, so Orb cannot determine how to create the transaction lines. | Add NetSuite item mappings for all affected Orb items and retry the failed sync. | ### Period / State Errors | **Sync error** | **What this means** | **Recommended remediation** | | :--------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------ | | Closed accounting period | NetSuite rejected the transaction because it is dated in an accounting period that is closed to posting or mutation. | Either reopen the accounting period in NetSuite and retry, or manually reconcile the transaction in NetSuite if reopening the period is not acceptable. | | Transaction already voided | Orb attempted to void or mutate a NetSuite transaction that has already been voided. | Review whether any further action is actually needed, and avoid retrying unless the related Orb and NetSuite records are first reconciled. | | Invoice payment already applied | Orb attempted to apply a payment to an invoice that NetSuite already considers paid. | Confirm whether the payment has already been applied correctly in NetSuite, and if so mark the issue resolved rather than retrying blindly. | | Customer balance already applied | The Orb transaction depends on customer balance behavior that cannot be completed automatically through the standard NetSuite sync path. | Follow the guided remediation flow in the UI to manually reconcile the customer balance scenario in NetSuite before resolving the sync. | | Applied credit block voided | Orb is trying to void a credit block that has already been applied downstream, which requires manual accounting cleanup instead of an automatic sync. | Follow the guided remediation flow in the UI to manually unwind the applied credit block state in NetSuite and then resolve the sync. | | A duplicate record already exists in NetSuite. | There’s an idempotency collision for this record. | Do not re-sync – this record has already been synced. | ### Transaction / Data Errors | **Sync error** | **What this means** | **Recommended remediation** | | :-------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Transaction not balanced | NetSuite rejected the transaction because the line-level amounts do not add up to a balanced transaction, often due to rounding or line-item discrepancies. | Review the transaction totals, line amounts, taxes, and adjustments for inconsistencies, then correct the underlying data and retry; escalate to Orb if the mismatch is not obvious. | | Transaction linking failed | Orb could not link the NetSuite transaction to the expected invoice or related record, usually because of customer mismatches or inconsistent accounting data. | Verify that the related customer and transaction records match across Orb and NetSuite, fix any data inconsistencies, and retry the sync. | | Global tax processing error | NetSuite failed while processing the tax data included in the sync payload. | Review the tax configuration, tax codes, and transaction tax data in both Orb and NetSuite, then retry after correcting the tax setup. | # What Is Synced Source: https://docs.withorb.com/integrations-and-exports/netsuite-what-is-synced Orb “syncs” data to NetSuite when transactions are finalized, including invoice issuance, credit note creation, or payment application. Orb’s integration is NOT journal entry-based – we create **first-class, native records** in NetSuite that allow you to  1. Leverage existing configuration preferences (item-level revenue recognition rules, GL account mapping, transaction form customizations). 2. Preserve a robust audit trail and complete system of record in your ERP. | **Orb Record Type** | **What we send (Orb)** | **Where it’s received (NetSuite)** | **When it’s synced** | | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- | | **Customer** |
  1. Name
  2. Email
  3. Billing address
  4. Shipping address
  5. External ID
  6. (Optional) mapped metadata at creation time
|
  1. Company name
  2. Email
  3. defaultBilling in CustomerAddressbook
  4. defaultShipping in CustomerAddressbook
  5. Orb External Customer ID *(bundle custom field)*
  6. Mapped entity custom field
| When the first invoice is issued from Orb. *Note: Assumes first-time creation is enabled in integration settings.* | | **Subscription** |
  1. Subscription dates
  2. Subscription ID
|
  1. Start date, End date
  2. Orb Subscription ID *(bundle custom field)*
| When the first invoice is issued from Orb. *Note: only relevant for sales order based mappings of fixed-term subscriptions.* | | **Invoice** | **Transaction level**
  1. Invoice date
  2. Invoice due date
  3. Invoice number
  4. Invoice memo
  5. Subsidiary
**Line level**
  1. Item
  2. Name
  3. Rate
  4. Quantity
  5. Amount
  6. Service period start date
  7. Service period end date
  8. Taxable status (if applicable)
**Adjustments (only if applicable)**
  1. Subtotal
  2. Discount percentage
  3. Discount amount
  4. Coupon amount
  5. Maximum adjustment
  6. Minimum adjustment
| **Transaction level**
  1. Date
  2. Due date
  3. Orb invoice number *(bundle custom field)*
  4. Memo
  5. Subsidiary
**Line level**
  1. Item
  2. Description
  3. Rate
  4. Quantity
  5. Amount
  6. Orb rev rec start date *(bundle custom field)*
  7. Orb rev rec end date *(bundle custom field)*
  8. Tax
**Adjustments (only if applicable)**
  1. Orb list rate *(bundle custom field)*
  2. Orb discount percentage *(bundle custom field)*
  3. Orb discount amount *(bundle custom field)*
  4. Orb coupon applied *(bundle custom field)*
  5. Orb maximum adjustment *(bundle custom field)*
Orb minimum adjustment *(bundle custom field)* | When the invoice is issued from Orb. | | **Credit Note** | **Transaction level**
  1. Credit note creation date
  2. Credit note number
  3. Subsidiary
  4. Credit note memo
**Line level**\*Same as invoice.\*\*\*Adjustments (only if applicable)\*\**Same as invoice.* | **Transaction level**
  1. Date
  2. Orb credit note number *(bundle custom field)*
  3. Subsidiary
  4. Credit note memo
**Line level**\*Same as invoice.\*\*\*Adjustments (only if applicable)\*\**Same as invoice.* | When the credit note is created in Orb. | | **Payment** |
  1. Amount
  2. Customer
  3. Transaction date (payment effective date, not “marked as paid”)
  4. Bank account
  5. Currency
  6. Linked invoice
|
  1. Payment amount
  2. Customer
  3. Date
  4. Account
  5. Currency
  6. Applied to invoices
| When the payment is applied to an Orb invoice. | **Orb does not sync \$0 transactions or \$0 line items to NetSuite** This typically occurs when there has been no usage accrued in the billing period, or usage is fully covered by credits, resulting in no net billing activity. Since there is no GL impact, there is nothing for NetSuite to act on. Orb remains the system of record for \$0 invoice history. ## Orb customer metadata sync Orb’s NetSuite integration supports syncing metadata on an Orb customer record, to a designated customer entity field in NetSuite. * This sync happens only during initial customer creation. Any metadata added or changed on the customer record after creation in NetSuite will not be synced. * Only free-form text type entity fields are eligible for metadata mapping. To successfully sync customer metadata, the following must be configured before syncing a customer via the integration: 1. Customer metadata must be added and populated on the customer record in Orb 2. The custom entity field must be configured, and scoped to Customer in NetSuite 3. The metadata mapping must be completed in the integration configuration flow # NetSuite (Legacy) Source: https://docs.withorb.com/integrations-and-exports/netsuite_legacy Orb's NetSuite integration has been rebuilt with significant improvements to reliability, transaction handling, and configurability. All new customers are onboarded to the Current integration. Existing customers remaining on this Legacy integration will eventually be migrated. Please reach out to Orb for migration support or to confirm which integration you're on. Orb's integration with NetSuite syncs a copy of invoices from Orb to NetSuite to power downstream workflows like invoicing, reporting, revenue recognition, and recordkeeping directly in your ERP. ## Getting started ### Prerequisites 1. NetSuite account 2. Sufficient administrator settings in your NetSuite instance to install bundles and enable SuiteCloud features ### Connecting NetSuite and Orb 1. To get started with setup, navigate to the [Integrations](https://app.withorb.com/settings?tab=integrations) tab of your account Settings **in Orb.** * Depending on if you're connecting to NetSuite for invoicing, or strictly for accounting purposes (you invoice using Orb or a different provider), click "Connect to Netsuite" in the relevant section. netsuite-connect-button 2. **In NetSuite**, do the following to establish the connection: 1. Install the `AccountLink NetSuite Bundle`, and the [AccountLink: Rev Rec Columns Bundle](https://system.netsuite.com/app/bundler/bundledetails.nl?id=493493\&sourcecompanyid=7500456). * \_Note: \_`The AccountLink: Rev Rec Columns Bundle`*adds two custom fields to the Netsuite instance in order to support syncing start and end dates for invoice line item service periods.* * Alternatively, if the direct link to install the `Account Link: Rev Rec Columns Bundle` is not working, navigate to the [Search & Install Bundles](https://system.netsuite.com/app/bundler/installbundle.nl) in Netsuite to find`AccountLink: Rev Rec Columns Bundle`. 2. Enable SuiteCloud features as listed. 3. Confirm setup. 4. Upon clicking Save, you should now see NetSuite as a connection at the top of the Integrations tab, with the status “Sync in progress" **in Orb**. 5. Once existing data from NetSuite is synced and ready to be used by Orb, the status of the connection will show up as “Configuration required”. Until then, the connection cannot be used in Orb. This can take up to a few hours, depending on the amount of data in the connected NetSuite account. 3. **In Orb**, finish configuring the connection. 1. Within the Integrations tab, go to either the Invoicing or Accounting section, depending on which integration you want to use, and click “Configure” in the NetSuite card. 2. Select a subsidiary and a default bank account to connect your Orb account to. You will need to select a bank account per currency that you support in Orb. 3. If you've configured the Location field to be *mandatory* in your NetSuite Accounting Preferences, select the corresponding location from the dropdown. 4. (Optional) Sync taxes to NetSuite. Taxes will be synced as a separate line item when using the NetSuite Accounting Sync. Select an external item to sync taxes to. 5. \[Accounting sync only] If configuring the connection for the Accounting sync, there is an extra configuration step to define additional criteria for which invoices are synced. * If you already have customers in your accounting provider, map Orb customers to existing customer records **before** turning on the “Create customer records” setting, so that duplicate customer records are not generated in your accounting provider. You can enable this setting later if you are not ready to map customers. * Instructions for mapping customers can be found [here](https://docs.withorb.com/integrations-and-exports/netsuite#configuring-customers). 6. (Optional) Map Orb items to their external item in NetSuite, then click “Save”. * Mapping Orb items to items in NetSuite is required for Orb to sync invoices to NetSuite. This can be completed later if you are not ready to map items. * Instructions for setting up items in NetSuite can be found [here](https://docs.withorb.com/integrations-and-exports/netsuite#mapping-items). 7. While setting up customers and items in NetSuite, if data is missing in Orb, click the “Refresh” button to re-sync data from NetSuite. Orb also periodically updates data from NetSuite. ### What is synced Orb's integration syncs invoices, credit notes, and payment records to NetSuite, in addition to the associated customer and subscription details. * Invoices * Invoice number * Line items (totals, price, quantity, service periods, adjustments) * Each Orb invoice line item creates a matching NetSuite line item * Service periods (start and end dates) match exactly between Orb and NetSuite * Tax * Due date * Invoice date * Credit notes * Credit note number * Line items * Tax * Issue date * Payments * Amount ## NetSuite guidance ### Setting up RevRec schedules Orb syncs line item service periods so you can perform accurate revenue recognition in NetSuite. To enable this: 1. In NetSuite, navigate to the Revenue Field Mapper. 2. Map the `Rev Rec Start Date` and `Rev Rec End Date` source fields to the corresponding Start Date and End Date end fields, respectively. * Start and end dates synced to NetSuite match service period dates as they appear in Orb. netsuite-rev-rec-field-mapping 3. Navigate to Revenue Recognition Rule, and create a new rule. * Specify the `Rev Rec Start Date Source` and `Rev Rec End Date Source` to the data that mapped in the previous step. netsuite-rev-rec-rule ### Setting up Items Mapping Orb items to items in NetSuite is required for the integration to function as expected. Without item mapping, Orb will not be able to successfully sync invoices to Netsuite correctly. In NetSuite, create items corresponding to items you are selling in Orb. When creating the item, in the Revenue Recognition/Amortization tab, select the Revenue Recognition Rule and Rev Rec Forecast Rule you've previously created. netsuite-item ### Mapping items Orb supports many-to-one item mapping, where each item in Orb maps to exactly one item in NetSuite, but multiple Orb items can map to the same NetSuite item if desired. This provides flexibility in organizing your product catalog while maintaining clean revenue recognition. There are two ways to map items: * Option 1: Navigate to the Items tab **in Orb** under Settings, select an item, click "Edit" and select the corresponding NetSuite item. netsuite-mappings * Option 2: Navigate to the Integrations tab **in Orb** under Settings, and in the NetSuite card, click the “Edit” button and go to the “Map items” step to view a list of all items and select NetSuite items for each. ## Frequently asked questions ### Does the integration create standard NetSuite Invoice records, or a custom record type? The integration creates standard NetSuite Invoice records, not custom record types. This ensures compatibility with existing NetSuite workflows and reporting. ### Can Orb unbundle subscription products (SaaS and usage) before syncing to NetSuite, and map each component to distinct NetSuite items for rev rec purposes? Yes. For each Orb invoice line item, there will be a matching NetSuite line item on the corresponding NetSuite invoice. Orb's invoice line items are mapped to NetSuite items using the underlying Orb item resource, which can be managed centrally in the [Items settings](https://app.withorb.com/settings?tab=items) in Orb. This allows you to unbundle subscription products and map each component to distinct NetSuite items for revenue recognition purposes. ### Are start and end dates reliably populated for all subscription and usage-based charges, including when items are bundled? Yes, the start and end dates for each line item in NetSuite will match the start and end dates of the corresponding Orb line item's service period. This applies to all subscription and usage-based charges, including bundled items. ### If fair value allocations are required, does Orb handle the allocation logic or is it expected to occur in NetSuite? Fair value allocations are expected to be handled in NetSuite. If you have specific requirements around fair value pricing or SSP Category data to support NetSuite ARM, please reach out to discuss your use case in more detail. ### Is item mapping one-to-one between Orb and NetSuite, or can it support one-to-many or many-to-one mappings? Item mapping is "many-to-one" - each item in Orb maps to exactly one item in NetSuite, but many Orb items can map to the same NetSuite item if desired. This provides flexibility in how you organize your product catalog while maintaining clean revenue recognition in NetSuite. ### Testing NetSuite Connect a NetSuite sandbox account to Orb in Test mode. While testing RevRec, syncing invoices does not automatically update revenue arrangements in Netsuite. To force update the revenue arrangement within NetSuite, in Update Revenue Arrangements and Revenue Plans, click Refresh, Update Revenue Arrangements, and then Update Revenue Plans. ## Best practices: using NetSuite for invoicing ### Configuring customers After item mapping has been completed the last step for the invoicing provider is to create or update customers in the Orb UI, or through the API. When creating or updating customers in the Orb UI, select NetSuite as the Payment Provider in order to view a list of previously created customers in your NetSuite account. Customer mappings can also be configured in the API, by passing in the customer’s NetSuite ID under `payment_provider_id`, and setting `payment_provider` to `netsuite`. The customer’s NetSuite ID can be found by navigating to the customer’s page in NetSuite, and copying the ID from the URL (e.g. `https://<>.app.netsuite.com/app/common/entity/custjob.nl?id=1613&whence=`, where `1613` is the ID) Once mappings are configured, invoices for customers with a NetSuite payment provider ID will be synced to NetSuite. ### Mapping Orb Invoice Number to a NetSuite invoice Optionally, it's possible to map the Invoice Number in Orb to a NetSuite invoice. For full feature parity, we recommend this as a best practice. There are two options to enable this: 1. Option 1: If you wish to keep unique identifiers for invoices in NetSuite, you should use the [Using Custom Fields](#using-custom-fields) instructions. 2. Option 2: If you intend to use Orb's Invoice Number as your unique identifier, you can use [Mapping NetSuite Invoice Document Number](#mapping-netsuite-invoice-document-number). **Option 1: Using Custom Fields** After logging in to your NetSuite account, please follow these instructions to add the necessary custom fields. You can reach the "Add Custom Field Form" by hovering on Customization > Lists, Records, & Fields > Transaction Body Fields, then clicking on "New" Please add the Orb Invoice Number field by filling out the form, then click "Save": * LABEL: Orb Invoice Number * ID: \_orb\_invoice\_number * OWNER: Your account name should automatically be listed here * DESCRIPTION: The identifying Number for the Orb generated invoice. * Applies To: Check only "Sale" As invoices sync, you should see the Orb Invoice Number custom column on all invoices, and new invoices will have the field populated. **Option 2: Mapping NetSuite Invoice Document Number** After logging in to your NetSuite account, please follow these instructions to directly map the Orb Invoice Number to the NetSuite Invoice Document Number. Note that this approach is potentially dangerous, as it can lead to duplicate invoice numbers in NetSuite. If you are unsure, please use the [Using Custom Fields](#using-custom-fields) instructions. You can find more information on this topic in [NetSuite's docs](https://docs.oracle.com/en/cloud/saas/netsuite/ns-online-help/bridgehead_4340447524.html). 1. Navigate to Setup > Company > Auto-Generated Numbers (or use the search bar) 2. Click on the "Document Numbers" Tab 3. Check the box in the Invoice row, for the "Allow Override" column 4. Click "Save" 5. Review the prompt, and if you agree, press "Ok" The result of this is that all new invoices will have the Orb Invoice Number visible in the "Invoice #" field in NetSuite. # QuickBooks Source: https://docs.withorb.com/integrations-and-exports/quickbooks ## Prerequisites 1. QuickBooks account ## Connecting QuickBooks with Orb 1. In the sidebar of the Orb dashboard, click "Settings", then click on the “Integrations” tab. Click "Connect to QuickBooks" from either the Invoicing section or Accounting section, depending on which integration you plan on using. quickbooks-connect-button 2. Follow the instructions to connect QuickBooks 3. \[Optional] Sync taxes to Quickbooks. After connecting to QuickBooks click the "Configure" button. Taxes will be synced as a separate line item when using the QuickBooks Accounting Sync. Select an external item in quickbooks to map taxes to. 4. If configuring Accounting sync, there will be an extra step to configure invoice accounting sync settings. Note: If you already have customers in your accounting provider, map Orb customers to existing customer records **before** turning on the “Create customer records” setting, so that duplicate customer records are not generated in your accounting provider. You can enable this setting later if you are not ready to map customers. Instructions for mapping customers can be found [here](https://docs.withorb.com/integrations-and-exports/netsuite#configuring-customers). To create customers with duplicate names in Orb, enable appending an external id to the customer name to successfully create and sync them as new, unique customers in Quickbooks. Accountingsync Pn 5. \[Optional] Map Orb items to their external item in QuickBooks, then click “Save”. Mapping Orb items to items in QuickBooks is required for Orb to sync invoices to QuickBooks. This can be completed later if you are not ready to map items, but invoices with unmapped items will fail to issue. ## Mapping items In QuickBooks, Items are called Products and Services, and can be found in the Get paid and pay menu (direct link [https://app.qbo.intuit.com/app/items](https://app.qbo.intuit.com/app/items)). Create a product or service in QuickBooks for every item that you are selling in Orb. There are two ways to map items: Navigate to the Items tab in Orb under Settings, select an item, click edit, and select a QuickBooks item for each Orb item. quickbooks-mapping Or, navigate to the Integrations tab in Orb under Settings, and in the QuickBooks card, click the “Edit” button and go to the “Map items” step to view a list of all items and select QuickBooks items for each. ## Using Orb for Invoicing? Go to Settings > Integrations > Accounting section to configure the sync. For more information on sync settings and details go [here](/integrations-and-exports/accounting). ## Using Quickbooks as an external invoicing provider? ### Setup the Invoicing Integration Finish setting up the invoicing provider by mapping customers. ## Configuring customers After item mapping has been completed the last step for the invoicing provider is to create or update customers in the Orb UI or through the API. When creating or updating customers in the Orb UI, select QuickBooks as the Payment Provider in order to view a list of previously created customers in your QuickBooks account. Customer mappings can also be configured in the API, by passing in the customer’s QuickBooks ID under `payment_provider_id`, and setting `payment_provider` to `quickbooks`. The customer’s QuickBooks ID can be found by navigating to the customer’s page in QuickBooks, and copying the ID from the URL (e.g. [`https://app.qbo.intuit.com/app/customerdetail?nameId=377`](https://app.qbo.intuit.com/app/customerdetail?nameId=377), where `377` is the ID) Once mappings are configured, invoices for customers with a QuickBooks payment provider ID will be synced to QuickBooks. # Overview Source: https://docs.withorb.com/integrations-and-exports/salesforce/index With Orb’s Salesforce integrations you can keep your sales and revenue operations teams embedded directly within Salesforce and still harness the power of Orb for revenue visibility, billing, and sales automation. With the integrations: * You can use insights from your Orb data to gain a more holistic view of account health, answering common questions about invoice totals or product usage. * You can automatically provision or upgrade a contract or a subscription to keep your subscription and contract changes timely and efficient. * You can amend contracts to represent the latest products and services a customer should receive as the result of an upsell. * You can create custom Salesforce reports to analyze plan distribution, SKU revenue, and credit burndowns. To power your sales-driven workflows, Orb offers two native integrations with Salesforce to support best-in-class revenue visibility and sales automation. * [CRM Integration](/integrations-and-exports/salesforce/salesforce-crm): Sync usage and financial resources into Salesforce from Orb to help your sales team understand the comprehensive state of each account. * [CPQ Integration](/integrations-and-exports/salesforce/salesforce-cpq): Sales automation integration that uses Salesforce Flow to trigger actions in Orb so you can automate provisioning and quote-to-cash workflows. The Salesforce integrations are not included with all Orb plans. Please reach out to your Orb representation if you have any questions or are interested in learning more. # Provision in Orb from Salesforce Source: https://docs.withorb.com/integrations-and-exports/salesforce/salesforce-cpq Use the Orb Flow Actions integration to automate your provisioning and quote-to-cash workflows. This guide includes installation, configuration, and common use cases you can power with the integration. You can also review [this video](https://www.loom.com/share/e1cc64e1036143da85bb0b07ad0123c1?sid=a9eebe72-ef92-453d-a26f-0d4d430462e1) to see an example set-up and workflow configuration. ### How it works Orb’s Flow Actions integration automates provisioning from your Salesforce instance to your Orb account based on custom triggers and flows built into the SFDC flow-builder. The integration is installed via a Salesforce managed package and includes endpoints available within Orb’s API packaged as Apex actions. Each API endpoint is fully supported within the Apex action so that you can uniquely power your company's workflows. Those actions can be triggered manually or upon standard SFDC triggers (e.g. “Opportunity moved to closed-won”). The Apex actions included have hybrid APIs that work directly in Flow Builder, but also work well when composed together in code. This is done by providing Apex action classes that are Flow Builder-compatible, but have a “raw JSON” input option when the builder UI is insufficient or cumbersome. ## Accessing the managed package If the Salesforce integration is included in your plan, you can access the links to download the managed package within the Orb web application. Note: Only Orb Admins have access to the Salesforce links. If you're not an Orb admin, reach out to the admin at your organization for support accessing the package links. To access the links: 1. Open the Orb web app ([app.withorb.com](https://app.withorb.com)) 2. Navigate to settings 3. Select the Integrations tab 4. Scroll to the Salesforce section You'll see a link to a sandbox managed package, recommended for syncing your SFDC sandbox with Orb's test mode for testing the integration. And a production package, recommended for syncing your SFDC production instance with Orb live mode. Installation of the package should be done by a Salesforce Admin using the workflow below. ## Installation Orb's Salesforce application is distributed as a [Salesforce Managed Package](https://help.salesforce.com/s/articleView?id=sf.s1_branded_apps_managed_package_about.htm\&language=en_US). Installation of the package should be done by a Salesforce Admin. 1. Navigate to the installation link provided, and click Install for Admins Only. Install salesforce 2. Once the package is installed, navigate to the App Launcher and search for the Orb app. Configuration salesforce ### Configuration On the configuration page for the Orb application, you'll be asked to provide an API key. This is an Orb API Key that is used to make requests via Orb’s SFDC package to the Orb API. While any valid API Key for your account can be used, we recommend provisioning a specific Salesforce API key for this purpose and identifying it appropriately. To provision an Orb API key, visit the [API Keys page](https://app.withorb.com/api-keys) in the Developers section of the Orb dashboard. SFDC configuration ## Use Cases | | Description | Orb API Endpoints | | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Provision new contracts | A new Enterprise contract is sold to an existing customer on a self-serve paid plan, an existing user of the free plan, or to a net new user. | In flow builder, fetch or create an Orb customer.

When a customer does not have an existing Orb subscription, use Create subscription:
- Equivalent APEX action: `OrbCreateSubscription`
- [API docs](/api-reference/subscription/create-subscription#create-subscription)

When a customer does have an existing Orb subscription, Schedule plan change:
- Equivalent APEX action: `OrbSchedulePlanChange`
- [API docs](/api-reference/subscription/schedule-plan-change#schedule-plan-change) | | Amend an existing contract | An existing contract is amended (edited), usually in an upsell or revision scenario. Often the following is amended:
- Usage quantities: Increasing usage commits or decreasing them
- Adding new products within the contract term | The APEX actions used depend on the contract changes made.

Update fixed-fee quantity:
- This is specifically for cases where you need to update a quantity on a non-usage-based fee; allocations are a special kind of non-usage-based fee, so allocation amounts can be updated this way
- Equivalent APEX action: `OrbUpdatePriceQuantity`
- [API docs](/api-reference/price-interval/add-or-edit-price-intervals#add-or-edit-price-intervals)

Add and edit price intervals:
- This endpoint lets you end prices on the subscription at specific dates, and add new ones
- These changes can be backdated or forward-dated
- Ending a price interval, and adding a new one with similar-but-different configuration, is the common way to “amend” a particular price point
- Equivalent APEX action: `OrbAddEditPriceIntervals`
- [API docs](/api-reference/price-interval/add-or-edit-price-intervals) | | Cancel and replace an existing contract | An existing contract is canceled and replaced. Typically this is an upsell scenario, if a customer is growing in their usage, they want to negotiate a cheaper rate. | You’ll likely model this with a plan change, scheduled at the time of the transition to the new contract (note: this can be backdated or forward-dated).
- Equivalent APEX action: `OrbSchedulePlanChange`
- [API docs](/api-reference/subscription/schedule-plan-change#schedule-plan-change) | | Renew contracts | At the end of the contract term, the sales person will negotiate and provision a new contract. | You might model this as:

A plan change:
- Equivalent APEX action: `OrbSchedulePlanChange`
- [API docs](/api-reference/subscription/schedule-plan-change#schedule-plan-change)

A cancellation, plus a new subscription (in the event it’s a “new billing relationship” - e.g., with M\&A):
- Equivalent APEX actions: `OrbCancelSubscription`, `OrbCreateSubscription`
- [API docs](/api-reference/subscription/cancel-subscription#cancel-subscription) for cancellation
- [API docs](/api-reference/subscription/create-subscription#create-subscription) for creation | | Early termination | An enterprise customer ends a contract prior to the end of term and is no longer a customer or is moved to an existing paid or free plan. | You’ll likely model this as either a cancellation (churn):
- Equivalent APEX action: `OrbCancelSubscription`
- [API docs](/api-reference/subscription/cancel-subscription#cancel-subscription)

Or a plan change (downgrade):
- Equivalent APEX action: `OrbSchedulePlanChange`
- [API docs](/api-reference/subscription/schedule-plan-change#schedule-plan-change)

Orb will track prorated refunds for in-advance fees, but won’t issue them automatically - you’ll need to disburse those funds manually in the case of a prorated refund. | ## Setting up your first flow High-level, the steps to automate creating a subscription and customer from an Opportunity are: 1. Create a new record triggered Flow. Make sure to select the `Include a Run Asynchronously path...` checkbox at the bottom when creating the Flow. This allows you to use the Orb actions. 2. Configure the flow to run when an Opportunity object is transitioned to the Closed/Won stage. 3. Under the `Run Asynchronously` path, add an Action and search for the `Create Orb Customer` action Create customer action salesforce 1. Fill in appropriate fields from the opportunity, account, and other built in or custom fields. * You can also create an Orb object by assigning a variable, to pass in more complex fields. For example, to create an `Address` variable, create an `Assignment` with a data type of `Apex-Defined`. Search for the `withorb__OrbAddressInput` Apex class, and fill in fields as needed. * You can use this variable as input to the `Create Orb Customer` action. 2. Next, add the `Create Orb Subscription` action. Similar to customer, fill in the appropriate fields. * To support custom pricing per customer, assign a variable of type `withorb__OrbPriceOverride` and fill in appropriate fields. This data is normally stored on the Opportunity Line Item. 3. Save and activate the Flow. To test it, transition an opportunity to Closed/Won. Flow salesforce For a detailed walkthrough, we recommend watching the [video](https://www.loom.com/share/e1cc64e1036143da85bb0b07ad0123c1?sid=a9eebe72-ef92-453d-a26f-0d4d430462e1). # Sync Orb data to Salesforce Source: https://docs.withorb.com/integrations-and-exports/salesforce/salesforce-crm Give your team full visibility into customer subscription data directly in Salesforce. By syncing billing and usage data from Orb, sales and success teams can track account health, identify expansion opportunities, and anticipate customer needs. Monitor key business metrics including: * Current subscriptions and product usage * Invoice history and month-over-month trends * Real-time usage patterns for upsell opportunities * Credit balance and commitment tracking ## How it works The Salesforce CRM integration creates a set of custom objects in Salesforce corresponding to Orb resources, as detailed below. After successfully authenticating a Salesforce instance, the initial sync will export all existing Orb data into Salesforce. After the initial sync, objects will be incrementally synced on a daily basis. | Orb resource | Salesforce custom object name | Description | | ----------------------------------------------------------------------------------- | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [Customers](/core-concepts#customer) | OrbCustomer | Representation of a business which includes name, address, and other information identifying the billable entity ([schema](/integrations-and-exports/salesforce/schema#customer)) | | [Invoice](/core-concepts#invoice) | OrbInvoice | Issued invoices for the customer if the subscription is still active. Each invoice includes line items and finalized usage and dollar value totals ([schema](/integrations-and-exports/salesforce/schema#invoice)) | | [Plan](/core-concepts#plan-and-price) | OrbPlan | A group of prices representing what customers could be charged ([schema](/integrations-and-exports/salesforce/schema#plan)) | | [Subscription](/core-concepts#subscription) | OrbSubscription | The link between a plan and a specific customer. Including any overrides to a plan ([schema](/integrations-and-exports/salesforce/schema#subscription)) | | [Invoice Line Item Billing](/data-exports/resource-types#invoice-line-item-billing) | OrbInvoiceLineItemBilling | The issued and billed amounts for each line item on an invoice ([schema](/integrations-and-exports/salesforce/schema#invoice-line-item-billing)) | | [Subscription Version](/api-reference/subscription/fetch-subscription-schedule) | OrbSubscriptionVersion | A point in time representation of a plan for a particular customer ([schema](/integrations-and-exports/salesforce/schema#subscription-version)) | | [Daily Line Item Revenue](/data-exports/resource-types#daily-line-item-revenue) | OrbDailyLineItemRevenue | The daily incremental changes on a line item representing the quantities each day for an invoice ([schema](/integrations-and-exports/salesforce/schema#daily-line-item-revenue)) | | [Credit Ledger Entry](/product-catalog/prepurchase#exporting-the-ledger) | OrbCommittedLedgerEntry | Information on credit increments, decrements and voids ([schema](/integrations-and-exports/salesforce/schema#committed-ledger-entry)) | For schema object information refer to the [schema page](/integrations-and-exports/salesforce/schema). Salesforce custom objects ## Setup 1. Navigate to the Integrations page under Settings. 2. Click "Connect to Salesforce" to configure the data sync. Salesforce connect button 3. Authorize Orb to connect to your Salesforce instance. Orb census connection 4. After a successful connection, you can configure the sync. CRM configuration required ### Sync configuration Orb allows configuring the data types exported to Salesforce. Orb recommends syncing a smaller set of objects to avoid data limit restrictions in Salesforce. Once the selection has been submitted, Orb will begin the initial sync process to create the objects in Salesforce and begin syncing over the Orb objects. The data type selection can be modified once the integration is active. CRM configuration dialog ### Viewing Data in Salesforce Navigate to the Reports tab in Salesforce to view the synced data. Subscription usage # Schema Source: https://docs.withorb.com/integrations-and-exports/salesforce/schema ## Customer An Orb [customer](/api-reference/customer/fetch-customer) resource is synced to Salesforce as a custom object `OrbCustomer` with the following schema: | Orb Field Name | Salesforce Field Label | Salesforce Field Name | Datatype | | :--------------------------- | :------------------------ | :--------------------- | :--------------- | | id | Orb ID | Id | Text | | additional\_emails | Additional Emails | AdditionalEmails | Text Area (Long) | | auto\_collection | Auto-Collection | AutoCollection | Text | | balance | Balance | Balance | Text | | billing\_address | Billing Address | BillingAddress | Text Area (Long) | | created\_at | Created At | CreatedAt | DateTime | | currency | Currency | Currency | Text | | deleted\_at | Deleted At | DeletedAt | DateTime | | email | Email | Email | Email | | email\_delivery | Email Delivery | EmailDelivery | Text | | exempt\_from\_automated\_tax | Exempt From Automated Tax | ExemptFromAutomatedTax | Text | | external\_customer\_id | External Customer ID | ExternalCustomerId | Text | | metadata | Metadata | Metadata | Text Area (Long) | | name | Name | Name | Text | | payment\_provider | Payment Provider | PaymentProvider | Text | | payment\_provider\_id | Payment Provider ID | PaymentProviderId | Text | | portal\_url | Portal URL | PortalUrl | URL | | shipping\_address | Shipping Address | ShippingAddress | Text Area (Long) | | tax\_id | Tax ID | TaxId | Text | | timezone | Timezone | Timezone | Text | | updated\_at | Updated At | UpdatedAt | DateTime | ## Plan An Orb [plan](/api-reference/plan/fetch-plan) resource is synced to Salesforce as a custom object `OrbPlan` with the following schema: | Orb Field Name | Salesforce Field Label | Salesforce Field Name | Datatype | | :--------------------- | :--------------------- | :-------------------- | :--------------- | | id | Orb ID | Id | Text | | created\_at | Created At | CreatedAt | DateTime | | default\_invoice\_memo | Default Invoice Memo | DefaultInvoiceMemo | Text | | deleted\_at | Deleted At | DeletedAt | DateTime | | description | Description | Description | Text | | discount | Discount | Discount | Text Area (Long) | | external\_plan\_id | External Plan ID | ExternalPlanId | Text | | invoicing\_currency | Invoicing Currency | InvoicingCurrency | Text | | maximum | Maximum | Maximum | Text Area (Long) | | metadata | Metadata | Metadata | Text Area (Long) | | minimum | Minimum | Minimum | Text Area (Long) | | name | Name | Name | Text | | net\_terms | Net Terms | NetTerms | Number | | product | Product | Product | Text Area (Long) | | status | Status | Status | Text | | trial\_config | Trial Config | TrialConfig | Text Area (Long) | | updated\_at | Updated At | UpdatedAt | DateTime | | version | Version | Version | Number | ## Subscription An Orb [subscription](/api-reference/subscription/fetch-subscription) resource is synced to Salesforce as a custom object `OrbSubscription` with the following schema: | Orb Field Name | Salesforce Field Label | Salesforce Field Name | Datatype | | :------------------------------------ | :-------------------------------- | :---------------------------- | :-------------------- | | id | Orb ID | Id | Text | | active\_plan\_phase\_order | Active Plan Phase Order | ActivePlanPhaseOrder | Number | | auto\_collection | Auto-Collection | AutoCollection | Text | | billing\_cycle\_day | Billing Cycle Day | BillingCycleDay | Number | | created\_at | Created At | CreatedAt | DateTime | | current\_billing\_period\_start\_date | Current Billing Period Start Date | CurrentBillingPeriodStartDate | DateTime | | current\_billing\_period\_end\_date | Current Billing Period End Date | CurrentBillingPeriodEndDate | DateTime | | customer\_id | Customer | Customer | Lookup (Orb Customer) | | default\_invoice\_memo | Default Invoice Memo | DefaultInvoiceMemo | Text | | deleted\_at | Deleted At | DeletedAt | DateTime | | description | Description | Description | Text | | discount | Discount | Discount | Text Area (Long) | | end\_date | End Date | EndDate | DateTime | | invoicing\_threshold | Invoicing Threshold | InvoicingThreshold | Text | | maximum | Maximum | Maximum | Text Area (Long) | | metadata | Metadata | Metadata | Text Area (Long) | | minimum | Minimum | Minimum | Text Area (Long) | | net\_terms | Net Terms | NetTerms | Number | | plan\_id | Plan | Plan | Lookup (Orb Plan) | | start\_date | Start Date | StartDate | DateTime | | status | Status | Status | Text | | trial\_info | Trial Info | TrialInfo | Text Area (Long) | | updated\_at | Updated At | UpdatedAt | DateTime | ## Subscription Version An Orb [subscription version](/api-reference/subscription/fetch-subscription-schedule) resource is synced to Salesforce as a custom object `OrbSubscriptionVersion` with the following schema: | Orb Field Name | Salesforce Field Label | Salesforce Field Name | Datatype | | :------------------------ | :---------------------- | :-------------------- | :------------------------ | | id | Orb ID | Id | Text | | customer\_id | Customer | Customer | Lookup (Orb Customer) | | deleted\_at | Deleted At | DeletedAt | DateTime | | ended\_at | Ended At | EndedAt | DateTime | | modified\_at | Modified At | ModifiedAt | DateTime | | plan\_id | Plan | Plan | Lookup (Orb Plan) | | start\_date | Start Date | StartDate | DateTime | | subscription\_id | Subscription | Subscription | Lookup (Orb Subscription) | | subscription\_ended\_at | Subscription Ended At | SubscriptionEndedAt | DateTime | | subscription\_start\_date | Subscription Start Date | SubscriptionStartDate | DateTime | | updated\_at | Updated At | UpdatedAt | DateTime | ## Invoice An Orb [invoice](/api-reference/invoice/fetch-invoice) resource is synced to Salesforce as a custom object `OrbInvoice` with the following schema: | Orb Field Name | Salesforce Field Label | Salesforce Field Name | Datatype | | :--------------- | :--------------------- | :-------------------- | :------------------------ | | id | Orb ID | Id | Text | | created\_at | Created At | CreatedAt | DateTime | | customer\_id | Customer | Customer | Lookup (Orb Customer) | | currency | Currency | Currency | Text | | deleted\_at | Deleted At | DeletedAt | DateTime | | due\_date | Due Date | DueDate | DateTime | | invoice\_number | Invoice Number | InvoiceNumber | Text | | invoice\_type | Invoice Type | InvoiceType | Text | | issued\_at | Issued At | IssuedAt | DateTime | | memo | Memo | Memo | Text | | paid\_at | Paid At | PaidAt | DateTime | | plan\_id | Plan | Plan | Lookup (Orb Plan) | | status | Status | Status | Text | | subscription\_id | Subscription | Subscription | Lookup (Orb Subscription) | | total | Total | Total | Number | | updated\_at | Updated At | UpdatedAt | DateTime | | voided\_at | Voided At | VoidedAt | DateTime | ## Invoice Line Item Billing An Orb [invoice line item](/api-reference/invoice/fetch-invoice#response-line-items) resource is synced to Salesforce as a custom object `OrbInvoiceLineItemBilling` with the following schema: | Orb Field Name | Salesforce Field Label | Salesforce Field Name | Datatype | | :-------------------------- | :------------------------ | :---------------------- | :------------------------ | | id | Orb ID | Id | Text | | adjusted\_subtotal | Adjusted Subtotal | AdjustedSubtotal | Number | | amount | Amount | Amount | Number | | billable\_metric\_id | Orb Billable Metric ID | BillableMetricId | Text | | conversion\_rate | Conversion Rate | ConversionRate | Number | | correction\_amount | Correction Amount | CorrectionAmount | Number | | credits\_applied | Credits Applied | CreditsApplied | Number | | created\_at | Created At | CreatedAt | DateTime | | customer\_id | Customer | Customer | Lookup (Orb Customer) | | deleted\_at | Deleted At | DeletedAt | DateTime | | invoice\_id | Invoice | Invoice | Lookup (Orb Invoice) | | invoice\_line\_item\_id | Orb Invoice Line Item ID | InvoiceLineItemId | Text | | item\_id | Orb Item ID | ItemId | Text | | invoicing\_currency | Invoicing Currency | InvoicingCurrency | Text | | is\_partial\_invoice | Is Partial Invoice | IsPartialInvoiceID | Text | | name | Name | Name | Text | | price\_id | Orb Price ID | PriceId | Text | | partially\_invoiced\_amount | Partially Invoiced Amount | PartiallyInvoicedAmount | Number | | plan\_id | Plan | Plan | Lookup (Orb Plan) | | pricing\_currency | Pricing Currency | PricingCurrency | Text | | quantity | Quantity | Quantity | Number | | subscription\_id | Subscription | Subscription | Lookup (Orb Subscription) | | subtotal | Subtotal | Subtotal | Number | | tax\_amount | Tax Amount | TaxAmount | Number | | timeframe\_end | Timeframe End | TimeframeEnd | DateTime | | timeframe\_start | Timeframe Start | TimeframeStart | DateTime | | updated\_at | Updated At | UpdatedAt | DateTime | | voided\_at | Voided At | VoidedAt | DateTime | ## Daily Line Item Revenue An Orb [daily line item revenue](/data-exports/resource-types#daily-line-item-revenue) resource is synced to Salesforce as a custom object `OrbDailyLineItemRevenue` with the following schema: | Orb Field Name | Salesforce Field Label | Salesforce Field Name | Datatype | | :-------------------------- | :------------------------ | :---------------------- | :------------------------ | | id | Orb ID | Id | Text | | adjusted\_subtotal | Adjusted Subtotal | AdjustedSubtotal | Number | | amount | Amount | Amount | Number | | billable\_metric\_id | Orb Billable Metric ID | BillableMetricId | Text | | conversion\_rate | Conversion Rate | ConversionRate | Number | | correction\_amount | Correction Amount | CorrectionAmount | Number | | credits\_applied | Credits Applied | CreditsApplied | Number | | created\_at | Created At | CreatedAt | DateTime | | customer\_id | Customer | Customer | Lookup (Orb Customer) | | deleted\_at | Deleted At | DeletedAt | DateTime | | invoice\_id | Invoice | Invoice | Lookup (Orb Invoice) | | invoice\_line\_item\_id | Orb Invoice Line Item ID | InvoiceLineItemId | Text | | item\_id | Orb Item ID | ItemId | Text | | invoicing\_currency | Invoicing Currency | InvoicingCurrency | Text | | is\_partial\_invoice | Is Partial Invoice | IsPartialInvoiceId | Text | | name | Name | Name | Text | | price\_id | Orb Price ID | PriceId | Text | | partially\_invoiced\_amount | Partially Invoiced Amount | PartiallyInvoicedAmount | Number | | plan\_id | Plan | Plan | Lookup (Orb Plan) | | pricing\_currency | Pricing Currency | PricingCurrency | Text | | quantity | Quantity | Quantity | Number | | reverted\_at | Reverted At | RevertedAt | DateTime | | subscription\_id | Subscription | Subscription | Lookup (Orb Subscription) | | subtotal | Subtotal | Subtotal | Number | | timeframe\_end | Timeframe End | TimeframeEnd | DateTime | | timeframe\_start | Timeframe Start | TimeframeStart | DateTime | | updated\_at | Updated At | UpdatedAt | DateTime | ## Committed Ledger Entry An Orb [committed ledger entry](/api-reference/credit/fetch-customer-credits-ledger#fetch-customer-credits-ledger) resource is synced to Salesforce as a custom object `OrbCommittedLedgerEntry` with the following schema: | Orb Field Name | Salesforce Field Label | Salesforce Field Name | Datatype | | :----------------------- | :--------------------- | :-------------------- | :-------------------- | | id | Orb ID | Id | Text | | adjusted\_subtotal | Adjusted Subtotal | AdjustedSubtotal | Number | | amount | Amount | Amount | Number | | created\_at | Created At | CreatedAt | DateTime | | credit\_block | Credit Block | CreditBlock | Text Area (Long) | | currency | Currency | Currency | Text | | customer\_id | Customer | Customer | Lookup (Orb Customer) | | deleted\_at | Deleted At | DeletedAt | DateTime | | description | Description | Description | Text | | ending\_balance | Ending Balance | EndingBalance | Text | | entry\_status | Entry Status | EntryStatus | Text | | entry\_type | Entry Type | EntryType | Text | | event\_id | Orb Event ID | EventId | Text | | invoice\_id | Invoice | Invoice | Lookup (Orb Invoice) | | ledger\_sequence\_number | Ledger Sequence Number | LedgerSequenceNumber | Number | | metadata | Metadata | Metadata | Text Area (Long) | | name | Name | Name | Text | | new\_block\_expiry\_date | New Block Expiry Date | NewBlockExpiryDate | DateTime | | price\_id | Orb Price ID | PriceId | Text | | reverted\_at | Reverted At | RevertedAt | DateTime | | starting\_balance | Starting Balance | StartingBalance | Text | | updated\_at | Updated At | UpdatedAt | DateTime | | void\_amount | Void Amount | VoidAmount | Text | | void\_reason | Void Reason | VoidReason | Text | # Overview Source: https://docs.withorb.com/integrations-and-exports/stripe ## Setup ### Connecting Stripe with Orb 1. In the Orb dashboard, click the dropdown menu on the bottom left and navigate to Organization settings, navigate to the "Integrations" tab of the modal, and select Connect to Stripe 2. Follow the instructions to connect Stripe. Note: In **test** mode, Orb will connect to Stripe's test mode, allowing you to test safely with Stripe test customers and payment methods. When initiating the connection, please select "Skip this form" to proceed with the test mode connection. Stripe test mode connection prompt with Skip this form option ### Testing your integration When Orb is in **test** mode, it connects to Stripe's test mode, so you can run the full payment flow without affecting live customers or creating real charges. We recommend using Orb's test mode for initial testing, and have a step-by-step walkthrough of [how to collect payment against your first issued invoice](/tutorials/first-payment) which gives an example of the full Stripe payment flow. ### Configuring customers When creating or updating customers in the Orb UI, select Stripe as the Payment Provider in order to view a list of previously created customers in your Stripe account. Customer mappings can also be configured in the API, by passing in the customer's Stripe ID under `payment_provider_id`, and setting `payment_provider` to `stripe_invoice`. Note that the payment provider here is `stripe_invoice`. If you instead pass `stripe_charge`, we will use Stripe as a payment gateway, and not as an invoicing provider. The customer's Stripe ID can be found on the customer page, and starts with the prefix `cus_`. Once mappings are configured, invoices for customers with a Stripe payment provider ID will be synced to Stripe Invoicing. For synced invoices, [payment methods](https://docs.stripe.com/invoicing/payment-methods), [invoice display](https://docs.stripe.com/invoicing/hosted-invoice-page), and tax are controlled with Stripe's platform settings. When using Orb Invoicing with Stripe as a payment gateway (with `payment_provider` set to `stripe_charge`), Orb automatically determines which payment method to charge using Stripe's default payment method with intelligent fallback logic. For details on payment method selection, see [Payment method selection](/invoicing/payments#payment-method-selection). You can also [exclude specific payment method types](/invoicing/payments#excluding-payment-methods) on a per-customer basis—useful for enterprise contracts where you want to avoid credit card processing fees. ### Customer Address Syncing Configure Stripe Invoicing dialog with Sync customer addresses option Orb can sync customer addresses from Stripe to ensure billing and shipping information stays up to date via a setting in the Stripe configuration. Address synchronization occurs in two scenarios: 1. **When linking customers**: When a customer in Orb is linked to a customer in Stripe (either through the UI or API), Orb will automatically pull the customer's billing and shipping addresses from Stripe and update the corresponding Orb customer record. 2. **Via webhooks**: When Orb receives a `customer.updated` webhook from Stripe, the system will automatically sync any address changes to the linked Orb customer. This ensures that address information remains consistent between both platforms without manual intervention. The synchronized address information includes: * Billing address (street, city, state, postal code, country) * Shipping address (if different from billing address) This automatic synchronization helps maintain accurate customer information for tax calculations, invoice delivery, and compliance requirements. ## Stripe Invoicing Compared to using Stripe as a payment gateway, Orb also allows you to fully sync invoices to Stripe for delivery and payment connection. ### Mapping items for Stripe Invoicing The following instructions are only if you would like to use Stripe Invoicing to send and manage invoices. Mapping items to products in Stripe is required for the integration to succeed. If you do not map **all** items that exist in Orb to a corresponding Stripe item, invoices will fail to issue. In Stripe, items are called Products, and can be found by searching for the [product catalog](https://dashboard.stripe.com/products?active=true). Create a product in Stripe for every item that you are selling in Orb. If you want Stripe to apply tax to synced invoices, also set a tax code on each product. Then, navigate to the Items tab in Orb under Settings, and select a Stripe item for each Orb item. stripe-mappings ### Taxes When you sync invoices to Stripe, Orb does not add tax to them, even if you have a tax provider integration configured in Orb. Tax integrations configured in Orb only work with Orb Invoicing. If you have tax configured in Stripe — via [Stripe Tax](https://docs.stripe.com/tax) or tax rates — Stripe adds tax to the invoice when it finalizes it. If you don't, the invoice is issued without tax. ### Invoice Metadata When using Orb's Stripe Invoice Sync, Orb automatically adds metadata to Stripe invoices to enable cross-referencing between Orb and Stripe invoice resources. This metadata is added to every synced invoice and can be accessed through Stripe's API or dashboard. The following metadata fields are added to each Stripe invoice: | Metadata Field | Description | | -------------------------- | ----------------------------------------------------------------------------------------------- | | `payment_source` | Always set to `orb` to identify invoices synced from Orb | | `orb_invoice_id` | The Orb invoice ID for cross-referencing | | `orb_subscription_id` | The Orb subscription ID, or empty if this is not a subscription invoice (e.g., one-off invoice) | | `orb_customer_id` | The Orb customer ID | | `orb_external_plan_id` | The Orb external plan ID, or empty if it does not exist | | `orb_external_customer_id` | The Orb external customer ID, or empty if the customer does not have an external customer ID | This metadata enables you to: * **Reconcile invoices** between Orb and Stripe systems * **Track subscription relationships** across both platforms * **Maintain data consistency** when working with both Orb and Stripe APIs * **Build integrations** that can seamlessly map between Orb and Stripe resources You can access this metadata through the Stripe API when retrieving invoice objects, or view it in the Stripe Dashboard under the invoice's metadata section. ### Invoice Collection Behavior When syncing invoices from Orb to Stripe, **Orb's auto-collection setting does not control how Stripe collects payment**. Stripe provides two mechanisms for auto-collection: * `charge_automatically`: Stripe will attempt to automatically charge the customer's default payment method upon invoice finalization. * `send_invoice`: Stripe will send the invoice to the customer without immediately charging a card. When syncing an Orb invoice to Stripe, Orb will determine which option to use depending on the `due_date` of the invoice. * For invoices that are "due on issue" (net terms = 0), `collection_behavior` is set to `charge_automatically` * For invoices with non-zero net terms, `collection_behavior` is set to `send_invoice` * For invoices with a custom due date *in the past* (before invoice creation date), `due date` is set to `null` to accommodate limitations in Stripe Stripe's `auto_advance` field determines whether the invoice should be **automatically finalized** after it's created. Orb sets this `true`. # India e-Mandates Source: https://docs.withorb.com/integrations-and-exports/stripe-india-mandates Set up and test India e-Mandates with Stripe for recurring payment collection India e-Mandates allow you to collect a one-time payment authorization from Indian customers, enabling recurring or sporadic charges without requiring the customer to be present for each transaction. This is a requirement from the [Reserve Bank of India (RBI)](https://rbi.org.in//Scripts/NotificationUser.aspx?Id=11668) for recurring card payments, and Stripe supports this through their SetupIntent API with India-specific mandate options ([docs](https://docs.stripe.com/india-recurring-payments)). When an e-Mandate is properly configured in Stripe and linked to an Orb customer, Orb can automatically charge the customer's default payment method when issuing invoices — just like any other Stripe payment method. ## Why e-Mandates require extra setup Unlike standard card payments, India e-Mandates require the merchant to explicitly create a mandate with specific parameters (amount cap, interval, start date) as part of the payment method setup. This means you cannot simply attach a card to a Stripe customer — you must go through the SetupIntent flow with `mandate_options` configured for India. This additional step is often a source of confusion during testing, since there is no way to create an India e-Mandate directly from the Stripe dashboard. You must use the Stripe API (or a frontend integration with Stripe Elements) to collect the mandate. ## Prerequisites All testing should be done in **Stripe test mode** and **Orb test mode**. Using live mode keys during testing can result in real charges to customers. In Orb, ensure you are operating in your test environment before connecting Stripe or creating customers. Before setting up an India e-Mandate, ensure the following: * Your Stripe **test mode** account is connected to Orb in **test mode** (see [Connecting Stripe with Orb](/integrations-and-exports/stripe#connecting-stripe-with-orb)) * You have access to your Stripe **test mode** API keys (`sk_test_` and `pk_test_` prefixed keys from the [Stripe API keys page](https://dashboard.stripe.com/test/apikeys)) * You have a test customer created in the [Stripe test dashboard](https://dashboard.stripe.com/test/customers) ## How it works The e-Mandate collection flow involves three steps: 1. **Create a SetupIntent** with India-specific mandate options via the Stripe API 2. **Collect the card details** from the customer using Stripe Elements on the frontend 3. **Set the payment method as default** on the Stripe customer so Orb can use it for auto-collection ### SetupIntent mandate options When creating a SetupIntent for an India e-Mandate, you must include `mandate_options` under `payment_method_options.card`. The key parameters are: | Parameter | Description | | ----------------- | ------------------------------------------------------------------------------------------- | | `reference` | A unique identifier for the mandate (e.g., combining the customer ID and a timestamp) | | `amount` | The maximum amount that can be charged per interval, in the smallest currency unit | | `amount_type` | Set to `"maximum"` to define a cap on the charge amount | | `currency` | The currency for the mandate (e.g., `"inr"`) | | `interval` | How often charges can occur — `"sporadic"` for on-demand or `"monthly"` for regular billing | | `start_date` | Unix timestamp for when the mandate becomes active | | `supported_types` | Must include `"india"` to indicate this is an India-specific mandate | The SetupIntent should also specify `usage: "off_session"` to indicate that the payment method will be used for charges when the customer is not present. The `amount` field represents the **maximum** amount that can be charged per transaction, not a fixed charge amount. Set this to a value that accommodates your highest expected invoice amount for the customer. ### Confirming the SetupIntent On the frontend, use Stripe Elements to render a payment form and collect the customer's card details. When the customer submits the form, confirm the SetupIntent using `stripe.confirmSetup()`. Stripe handles the mandate authorization as part of this confirmation step. ### Setting the default payment method After the SetupIntent is confirmed, the resulting payment method must be set as the customer's default in Stripe: ```javascript theme={null} await stripe.customers.update(customerId, { invoice_settings: { default_payment_method: paymentMethodId }, }); ``` This ensures that Orb will use this payment method when auto-collecting invoices. ## Testing with the example application We provide a [reference implementation](https://github.com/orbcorp/stripe-mandate-example) that demonstrates the full e-Mandate collection flow using a Node.js/Express backend and a Stripe Elements frontend. ### Setup Please follow the instructions in the [reference implementation](https://github.com/orbcorp/stripe-mandate-example/blob/main/README.md) to set up the example application. ### Test card details Use the following Stripe test card to simulate an India e-Mandate: | Field | Value | | ----------- | ------------------ | | Card number | `4000003560000123` | | Expiry | Any future date | | CVC | Any 3 digits | | Postal code | Any 5 digits | ### What to expect After submitting the form with the test card: 1. The SetupIntent is confirmed and the mandate is created in Stripe 2. The payment method is set as the customer's default 3. Approximately one minute later, a `mandate.updated` webhook event fires (visible in the [Stripe developers tab](https://dashboard.stripe.com/test/events)) 4. The payment method appears under "Payment methods" in the [customer's Stripe dashboard page](https://dashboard.stripe.com/test/customers) The example application uses a simple implementation that generates a unique mandate reference on server startup. You will need to restart the server (`Ctrl+C` then `node server.js`) between mandate setups. ## Connecting to Orb Once the e-Mandate is set up and the payment method is the customer's default in Stripe, connect it to Orb: 1. Ensure the Stripe customer is [mapped to an Orb customer](/integrations-and-exports/stripe#configuring-customers) with `payment_provider` set to `stripe_charge` or `stripe_invoice`. 2. Verify that [auto-collection](/invoicing/payments#auto-collection) is enabled for the customer. 3. Orb will automatically sync the default payment method from Stripe and use it when issuing invoices. When Orb issues an invoice for this customer, it will charge the e-Mandate payment method through Stripe. The mandate's `amount` cap must be sufficient to cover the invoice amount, or the charge will be declined by Stripe. If the invoice amount exceeds the mandate's configured `amount` cap, Stripe will decline the charge. Ensure the mandate amount is set high enough to accommodate your expected invoice amounts. There is a maximum amount cap of 15,000 INR for India e-Mandates. Anything above this amount will be rounded down to 15,000 INR. ## Troubleshooting ### Mandate not appearing on the customer Ensure the SetupIntent was confirmed successfully and the payment method was explicitly set as the customer's default via `invoice_settings.default_payment_method`. Simply attaching a payment method is not sufficient — it must be set as the default. ### Charges failing after mandate setup * Verify the mandate's `amount` cap is higher than the invoice amount * Check that the mandate's `start_date` is in the past (the mandate must be active) * Confirm the `mandate.updated` webhook event has fired in Stripe (this indicates the mandate is fully active) ### Webhook events to monitor The following Stripe webhook events are relevant to the e-Mandate lifecycle: | Event | Description | | ------------------------------- | --------------------------------------------------------------------------------------- | | `setup_intent.succeeded` | The SetupIntent was confirmed and the mandate was created | | `mandate.updated` | The mandate status has changed (fires \~1 minute after creation when it becomes active) | | `payment_intent.succeeded` | A charge against the mandate was successful | | `payment_intent.payment_failed` | A charge against the mandate failed | # Shared Payment Tokens Source: https://docs.withorb.com/integrations-and-exports/stripe-shared-payment-tokens Use Stripe Shared Payment Tokens (SPTs) for agent-facilitated commerce Shared Payment Tokens (SPTs) are Stripe's mechanism for agent-facilitated commerce. An SPT is a temporary, scoped payment credential granted by an AI agent on behalf of a customer. SPTs allow Orb to collect payment without requiring the customer to have a stored payment method on file. For more on SPTs from Stripe, see [Stripe's SPT documentation](https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens). This feature requires enablement on your account. Contact your Orb account team to get started. ## How it works 1. An AI agent, acting on behalf of a customer, negotiates a purchase 2. The agent grants an SPT to you (the seller/merchant) via Stripe's agentic commerce API 3. You configure the SPT on the customer in Orb 4. Orb uses the SPT to collect payment, either on-demand or via auto-collection When Orb creates a PaymentIntent with an SPT, Stripe handles the payment using credentials carried by the token, which resolves to a Payment Method in Stripe. ## Prerequisites * A Stripe Connect integration with Orb * The feature enabled on your account * An SPT ID (formatted as `spt_...`) obtained through Stripe's agentic commerce flow ## Configuring a default SPT on a customer Set a default SPT on a customer so Orb uses it for auto-collection: ```bash theme={null} curl https://api.withorb.com/v1/customers/{customer_id} \ -X PUT \ -H "Authorization: Bearer $ORB_API_KEY" \ -d '{ "payment_configuration": { "payment_providers": [{ "provider_type": "stripe", "default_shared_payment_token": "spt_..." }] } }' ``` To clear a stored SPT: ```bash theme={null} curl https://api.withorb.com/v1/customers/{customer_id} \ -X PUT \ -H "Authorization: Bearer $ORB_API_KEY" \ -d '{ "payment_configuration": { "payment_providers": [{ "provider_type": "stripe", "default_shared_payment_token": null }] } }' ``` You can also set or clear the SPT from the Orb dashboard via the **Edit customer details** dialog under the **Payments** section. ## Collecting payment ### Auto-collection with a stored SPT When a customer has a default SPT configured, Orb will automatically use it for invoice auto-collection. The SPT always takes priority over the customer's default payment method in Stripe. If the SPT fails at charge time, the invoice payment will fail. Orb does not clear the stored SPT automatically or fall back to the customer's default payment method, because this would provide a way to circumvent the limits of the SPT. The SPT must always be cleared by your integration. If the SPT has not been deactivated explicitly (e.g. it's exhausted temporarily), you may choose to keep the SPT as the payment method and increase the maximum allowance, or prompt your end-user to do so. ### One-off payment with an SPT You can pay a specific invoice with an SPT without storing it on the customer: ```bash theme={null} curl https://api.withorb.com/v1/invoices/{invoice_id}/pay \ -X POST \ -H "Authorization: Bearer $ORB_API_KEY" \ -d '{ "shared_payment_token_id": "spt_..." }' ``` If no request body is provided, the endpoint falls back to the customer's default payment method (existing behavior). ## SPT lifecycle ### Reuse SPTs support recurring use within Stripe's mandate limits. A single token can be used for multiple PaymentIntents over time. Stripe enforces a maximum amount — multiple charges are allowed within that limit if it's a recurring shared payment token. ### Expiration and deactivation Stripe manages SPT expiry. When a token is deactivated (revoked by the agent or expired), Stripe sends a `shared_payment.granted_token.deactivated` webhook. Orb processes this event and logs it. We also encourage you to listen for this webhook to clear the payment token in Orb. If a deactivated or exhausted SPT is used at charge time, Stripe will reject the PaymentIntent synchronously. Orb handles this as a terminal failure for the invoice collection process. ### Dunning Orb does not start a [dunning](/financial-operations/essential-dunning) schedule when an SPT payment fails. Orb also does not clear SPTs automatically to prevent policy bypass. ## What's supported | Capability | Status | | ------------------------------------------- | ----------------------------------------------------------------------------------------------- | | Store a default SPT on a customer | Supported | | Auto-collection using stored SPT | Supported | | One-off invoice payment with SPT | Supported | | Automatic fallback on SPT failure | Not supported — Orb does not fall back to other payment methods to avoid bypassing SPT policies | | Dunning after SPT failure | Not supported | | Invoice portal (customer-initiated payment) | Not applicable — SPTs are for off-session, agent-facilitated payments | | SPT as a payment method in customer portal | Not supported — SPTs are managed via API or dashboard, not self-service | If an SPT payment fails, it will not trigger dunning. Orb will also not fall back to other payment methods to avoid bypassing purpose-set SPT policies. ## Integration guide ### Step 1: Obtain an SPT A customer's agent grants a shared payment token (`spt_...`) to your Stripe account through Stripe's agentic commerce API. This happens outside of Orb. Refer to [Stripe's SPT documentation](https://docs.stripe.com/agentic-commerce/concepts/shared-payment-tokens) for details. ### Step 2: Store the SPT on a customer Set the SPT as the default payment instrument for a customer via the Orb API, as [outlined above](#configuring-a-default-spt-on-a-customer). ### Step 3: Invoices are auto-collected using the SPT No additional integration is needed. When an invoice is issued for a customer with a stored SPT, Orb will automatically use it for payment collection. The SPT takes priority over the customer's default payment method. ### Step 4 (optional): Pay a specific invoice with an SPT To pay a single invoice without storing the SPT on the customer, use the [/pay endpoint](#one-off-payment-with-an-spt). ### Step 5: Handle webhooks Register for the following Stripe webhook events and forward them to Orb as you do today: | Event | Description | | ------------------------------------------ | --------------------------------------- | | `shared_payment.granted_token.used` | Confirms the SPT was used for a payment | | `shared_payment.granted_token.deactivated` | The SPT has been permanently revoked | Orb logs these events. When a deactivated SPT is used for payment, Stripe will reject the charge synchronously. Orb will not automatically clear the stored SPT — your integration should clear it via the API when you receive a deactivation event, or when the agent grants a replacement token. ### Step 6: Manage the SPT lifecycle Orb does not automatically clear stored SPTs. Your integration is responsible for: * **Clearing a deactivated SPT**: Update the customer with `"default_shared_payment_token": null` when you receive a deactivation webhook or know the token is no longer valid * **Replacing an exhausted SPT**: If the token's monthly limit is reached, Stripe will reject charges until the limit resets. You can either wait for the reset or have the agent grant a new token * **Removing an SPT to revert to normal payment**: Set `"default_shared_payment_token": null` to resume using the customer's default payment method ## FAQ **Can I use an SPT alongside a regular payment method?** Yes. The SPT takes priority for auto-collection when set. If the SPT fails, Orb will not fall back to the customer's default payment method — the invoice payment will simply fail. To revert to regular payment method behavior, clear the SPT via the API or dashboard. **What happens if the SPT's monthly limit is reached?** The PaymentIntent creation will fail synchronously. Orb will not clear the stored SPT since it's not deactivated, and dunning will not activate on the invoice. **Do I need to clear the SPT manually when it's deactivated?** Yes. You can clear it via the API or dashboard. **Can I validate an SPT before storing it?** Yes, you can do this on the client-side but Orb does not do this for you. If an invalid SPT ID is stored, it will fail at charge time. # Summary webhooks Source: https://docs.withorb.com/integrations-and-exports/summary-webhooks Summary webhooks reduce the size of invoice and subscription webhook payloads by sending a lightweight version of the core resource. When enabled, invoice payloads exclude line items and pricing details, and subscription payloads include only essential identification and status fields. This feature is enabled per-account. Contact your Orb representative to enable summary webhooks for your account. All invoice and subscription webhook event types are affected when this feature is turned on. ## Affected event types ### Invoice events The following invoice event types will send summary payloads when summary webhooks are enabled: * `invoice.issued` * `invoice.edited` * `invoice.invoice_date_elapsed` * `invoice.issue_failed` * `invoice.payment_processing` * `invoice.payment_succeeded` * `invoice.payment_failed` * `invoice.manually_marked_as_paid` * `invoice.manually_marked_as_void` * `invoice.undo_mark_as_paid` * `invoice.sync_succeeded` * `invoice.sync_failed` * `invoice.accounting_sync_succeeded` * `invoice.accounting_sync_failed` * `invoice.dunning_schedule_created` * `invoice.dunning_schedule_ended` * `invoice.dunning_schedule_reset` * `invoice.dunning_schedule_step_executed` ### Subscription events The following subscription event types will send summary payloads when summary webhooks are enabled: * `subscription.created` * `subscription.edited` * `subscription.started` * `subscription.ended` * `subscription.plan_changed` * `subscription.plan_version_change_scheduled` * `subscription.plan_version_changed` * `subscription.fixed_fee_quantity_updated` * `subscription.usage_exceeded` * `subscription.cost_exceeded` * `subscription.trial_ended` * `subscription.plan_change_scheduled` * `subscription.cancellation_scheduled` * `subscription.cancellation_unscheduled` * `subscription.accounting_sync_succeeded` * `subscription.accounting_sync_failed` ## Invoice payload ### Fields included | Field | Type | Description | | ------------------------------- | -------- | ------------------------------------------------ | | `id` | string | Invoice ID | | `invoice_number` | string | Auto-generated invoice number | | `invoice_date` | datetime | The scheduled date of the invoice | | `currency` | string | ISO 4217 currency string or `credits` | | `status` | string | `draft`, `issued`, `paid`, `synced`, or `void` | | `invoice_source` | string | `subscription`, `partial`, or `one_off` | | `total` | string | Total after discounts and minimums | | `amount_due` | string | Final amount to be charged | | `due_date` | datetime | When payment is due | | `customer` | object | Minified customer (`id`, `external_customer_id`) | | `subscription` | object | Minified subscription (`id`) | | `created_at` | datetime | When the invoice was created | | `hosted_invoice_url` | string | Customer-facing invoice portal URL | | `invoice_pdf` | string | Link to download the invoice PDF | | `memo` | string | Free-form text on the invoice PDF | | `will_auto_issue` | boolean | Whether the invoice will be automatically issued | | `eligible_to_issue_at` | datetime | When the invoice becomes eligible to issue | | `shipping_address` | object | Shipping address | | `billing_address` | object | Billing address | | `customer_tax_id` | object | Customer tax ID | | `customer_balance_transactions` | array | Customer balance transactions | | `credit_notes` | array | Associated credit note summaries | | `payment_attempts` | array | Payment attempt history | | `metadata` | object | Custom metadata | | `issued_at` | datetime | When the invoice was issued | | `paid_at` | datetime | When the invoice was paid | | `voided_at` | datetime | When the invoice was voided | | `scheduled_issue_at` | datetime | Scheduled issue time | | `auto_collection` | object | Auto-collection configuration | | `issue_failed_at` | datetime | When the invoice failed to issue | | `sync_failed_at` | datetime | When external sync failed | | `payment_failed_at` | datetime | Most recent payment failure time | | `payment_started_at` | datetime | Most recent payment attempt start time | ### Fields excluded The following fields are omitted from the summary invoice payload. Notably, `line_items` is the primary source of payload bloat for invoice webhooks. | Field | Type | Description | | ---------------- | ------ | ----------------------------------- | | `line_items` | array | The full breakdown of prices | | `subtotal` | string | Total before discounts and minimums | | `discount` | object | Deprecated invoice-level discount | | `discounts` | array | Invoice-level discounts | | `minimum` | object | Invoice-level minimum | | `minimum_amount` | string | Minimum amount | | `maximum` | object | Invoice-level maximum | | `maximum_amount` | string | Maximum amount | To retrieve the full invoice including line items, use the [GET /invoices/\{id}](/api-reference/invoice/fetch-invoice) endpoint. ## Subscription payload ### Fields included | Field | Type | Description | | ------------ | -------- | ---------------------------------------------- | | `id` | string | Subscription ID | | `name` | string | Subscription name | | `status` | string | `active`, `ended`, or `upcoming` | | `start_date` | datetime | When billing starts | | `end_date` | datetime | When billing ends (null if ongoing) | | `created_at` | datetime | When the subscription was created | | `plan` | object | Plan summary: `id`, `external_plan_id`, `name` | | `customer` | object | Customer summary: `id`, `external_customer_id` | | `metadata` | object | Custom metadata | ### Fields excluded The following fields are omitted from the summary subscription payload. The `customer` and `plan` fields are replaced with minified versions containing only identification fields. | Field | Type | Description | | ------------------------------------ | -------- | ------------------------------- | | `current_billing_period_start_date` | datetime | Current billing period start | | `current_billing_period_end_date` | datetime | Current billing period end | | `trial_info` | object | Trial configuration | | `active_plan_phase_order` | integer | Current plan phase | | `fixed_fee_quantity_schedule` | array | Fixed fee quantity schedule | | `default_invoice_memo` | string | Default memo for invoices | | `auto_collection` | boolean | Auto-collection setting | | `net_terms` | integer | Payment terms | | `redeemed_coupon` | object | Coupon redemption | | `billing_cycle_day` | integer | Billing cycle anchor day | | `billing_cycle_anchor_configuration` | object | Billing cycle anchor config | | `invoicing_threshold` | string | Invoicing threshold amount | | `price_intervals` | array | Price intervals | | `adjustment_intervals` | array | Adjustment intervals | | `discount_intervals` | array | Discount intervals (deprecated) | | `minimum_intervals` | array | Minimum intervals (deprecated) | | `maximum_intervals` | array | Maximum intervals (deprecated) | | `pending_subscription_change` | object | Pending subscription change | To retrieve the full subscription, use the [GET /subscriptions/\{id}](/api-reference/subscription/fetch-subscription) endpoint. ## Recommended integration pattern With summary webhooks enabled, we recommend the following pattern for webhook handlers: 1. **Receive** the webhook event. 2. **Read** the event type and resource `id` from the payload. 3. **Fetch** the full resource via API if detailed data is needed. 4. **Process** accordingly. This ensures your handler always acts on the most current state of the resource, avoiding issues with stale data in webhook payloads. Even without summary webhooks, fetching the latest resource state via API before processing is a best practice. Webhook payloads represent the resource state at the time the event was emitted, which may differ from the current state if subsequent changes have occurred. # Third-party managed integrations Source: https://docs.withorb.com/integrations-and-exports/third-party-integrations Externally created and maintained wrappers, connectors, and integrations. Several community-supported tools make connecting to and working with the Orb system easier. Below is a list of third-party integrations, produced and maintained by our great partners. | Integration | Description | Link | | ----------------------------- | ---------------------------------------------------------------------- | ----------------------------------------------------- | | Supabase External Sync Engine | Syncs Orb data into Supabase-managed environments. | [Github](https://github.com/supabase/orb-sync-engine) | | Supabase Orb FDW | PostgreSQL foreign data wrapper is used to query Orb from Supabase. | [fdw.dev](https://fdw.dev/catalog/orb/) | | Tembo Orb FDW | PostgreSQL foreign data wrapper was built by Tembo to access Orb data. | [Github](https://github.com/tembo-io/orb_fdw) | # Webhooks Source: https://docs.withorb.com/integrations-and-exports/webhooks In order to notify your system of asynchronous actions within the Orb platform, Orb provides a powerful, real-time webhooks system that can issue calls to an endpoint of your choosing when certain events take place. Webhooks allow your system or integration to take actions as a result of these events. Orb's supported webhook events can be useful for a wide variety of use cases: 1. **Resource lifecycle changes**: Set up entitlements in your service as a result of object creation, such as a `subscription.created` event. 2. **Balance events**: Send out an end-user email when a customer's pre-paid credit balance is nearing zero. 3. **Data exports**: Receive events when an export of resource data to a destination succeeds or fails. Delivery of Resource lifecycle changes webhooks typically occurs in near real-time, though occasional delays may occur. For alerts on usage, cost, and credit balance, we evaluate alerts (and therefore issue webhooks) several times a day, but the latency guarantee will be a function of your workload. This can be reduced with provisioning for customers on Growth and Enterprise plans. Webhooks payloads in Orb follow the format: ```json theme={null} { // Unique to this WebhookEvent resource, and can be used for idempotency (process-once) purposes "id": string, // ISO8601 `created_at` timestamp of the `WebhookEvent` resource "created_at": string, // Identifies the type of webhook event being triggered "type": string, // If a webhook is associated with a resource, the full resource will be serialized // in the webhook body. "": object, // Additional properties specific to this event "properties": object, } ``` ## Configuring webhooks Webhooks are configured to an `HTTPS` URL on your backend server (note that only the `HTTPS` scheme is accepted for the production version of your webhook configuration). Each webhook endpoint is associated with a *secret* (see the `X-Orb-Signature` clause below). By default, all webhook events are sent to every endpoint. ## Debugging webhooks In the Orb UI, you can test your webhook configuration to ensure that it's set up properly and ready to receive events from us. From the [webhooks portal](https://app.withorb.com/webhooks), you can select on any webhook endpoint to send a "test event." Webhook test If you need to manually trigger a webhook event to be sent to your server, you can also resend it in the UI. Resend webhook ## Webhooks retries Orb guarantees *at-least-once* delivery for a single webhook event to each of your endpoint. Orb expects a `2xx` within five seconds as a successful response to a webhook request. If the request returns an error or otherwise times out, Orb will attempt to retry the event on the following retry schedule, stopping when either retries are exhausted or the request is accepted: * 5 seconds * 5 minutes * 30 minutes * 2 hours * 5 hours * 10 hours * 10 hours In order for your system to handle duplicate notifications, we recommend storing the `id` of a webhook event temporarily in the webhook consumer and refusing to re-process an event if the same `id` has already been seen. ## Automatic disabling of failing webhook endpoints Any webhook endpoints that return a `5XX` HTTP status codes for more than 99% of deliveries over a 7-day period will be disabled automatically by Orb to preserve system stability. These endpoints can be re-enabled in the Orb UI. ## Webhook patterns Event types follow the pattern `.`, where `` is the root object that is related to the webhook, and the `` is the event that has taken place (e.g. `customer.created` is a newly created [Customer](/core-concepts#customer) resource). By convention, a key in the payload will match the `resource_name` prefix in `type` (e.g. if the `type` is `subscription.action`, the payload will contain the serialized subscription resource under the `subscription` key). ## Webhook Event Types: Resource Lifecycle Changes Orb currently issues the following webhooks for resource lifecycle changes: ### backfill.reflected Issued when a [backfill](/api-reference/event/fetch-backfill) is closed and its events have been processed as usage. | key | schema | | ------------ | ----------------------------------------------- | | `backfill` | [Backfill](/api-reference/event/fetch-backfill) | | `properties` | `{}` | ### backfill.reverted Issued when a [backfill](/api-reference/event/fetch-backfill) is reverted, reversing its effects. | key | schema | | ------------ | ----------------------------------------------- | | `backfill` | [Backfill](/api-reference/event/fetch-backfill) | | `properties` | `{}` | ### billable\_metric.edited Issued when a billable metric is edited. This webhook is only issued when metadata for the resource changes, as billable metrics are otherwise immutable. The `previous_attributes` object will be present and populated with the previous state of any billable metric attributes that have changed. | key | schema | | -------------------------------------------- | --------------------------------------------------- | | `billable_metric` | [Billable metric](/api-reference/metric/get-metric) | | `properties.previous_attributes.name` | `string` | | `properties.previous_attributes.description` | `string` | | `properties.previous_attributes.metadata` | `object` | ### credit\_note.issued Issued when a credit note is created. | key | schema | | ------------- | ----------------------------------------------------------- | | `credit_note` | [Credit note](/api-reference/credit-note/fetch-credit-note) | | `properties` | `{}` | ### credit\_note.marked\_as\_void Issued when a credit note is marked as void. | key | schema | | ------------- | ----------------------------------------------------------- | | `credit_note` | [Credit note](/api-reference/credit-note/fetch-credit-note) | | `properties` | `{}` | ### customer.balance\_transaction\_created Issued when the customer balance changes for an Orb customer. The `balance_transaction` object will be present and populated with the specific entry that modified the balance. | key | schema | | :------------------------------- | :----------------------------------------------------------------------- | | `customer` | [Customer](/api-reference/customer/fetch-customer) | | `properties.balance_transaction` | [Balance transaction](/api-reference/customer/list-balance-transactions) | ### customer.created Issued when a customer resource is created. | key | schema | | ------------ | -------------------------------------------------- | | `customer` | [Customer](/api-reference/customer/fetch-customer) | | `properties` | `{}` | ### customer.edited Issued when a customer is edited. The `previous_attributes` object will be present and populated with the previous state of any `Customer` attributes that have changed. | key | schema | | ---------------------------------------------------- | -------------------------------------------------- | | `customer` | [Customer](/api-reference/customer/fetch-customer) | | `properties.previous_attributes.payment_provider` | `string` | | `properties.previous_attributes.payment_provider_id` | `string` | | `properties.previous_attributes.auto_collection` | `boolean` | | `properties.previous_attributes.email` | `string` | | `properties.previous_attributes.name` | `string` | | `properties.previous_attributes.email_delivery` | `boolean` | | `properties.previous_attributes.metadata` | `object` | | `properties.previous_attributes.tax_id` | [TaxId](/api-reference/customer/fetch-customer) | | `properties.previous_attributes.shipping_address` | [Address](/api-reference/customer/fetch-customer) | | `properties.previous_attributes.billing_address` | [Address](/api-reference/customer/fetch-customer) | ### invoice.automation\_schedule\_step\_executed Issued when a [Collections Automations](/financial-operations/automations) schedule step executes for an invoice. One event fires per step, with one entry in `properties.actions` per action that ran (payment retry, email). Unlike most invoice events, the `invoice` key carries a condensed invoice object designed for running notification workflows without a follow-up API call. See [Collections Automations](/financial-operations/automations#webhooks) for the full payload reference and examples. | key | schema | | ---------------------------------------------- | --------------------------------------------------------------------------------------------------------------- | | `invoice` | Condensed invoice object. See [Collections Automations](/financial-operations/automations#invoice-information). | | `properties.automation_schedule_template_id` | `string` or `null` | | `properties.automation_schedule_template_name` | `string` or `null` | | `properties.step_id` | `string` | | `properties.label` | `string` | | `properties.scheduled_at` | `date-time` | | `properties.executed_at` | `date-time` | | `properties.actions` | `array` of action entries (`action_type`: `retry_payment` or `send_email`) | Each entry in `properties.actions` is discriminated by its `action_type`. `send_email` entries: | key | schema | | ------------- | ------------------ | | `action_type` | `"send_email"` | | `recipient` | `string` or `null` | | `sent` | `boolean` | `retry_payment` entries: | key | schema | | --------------------------------- | ----------------------------------------- | | `action_type` | `"retry_payment"` | | `payment_transaction_record_id` | `string` or `null` | | `payment_provider` | `string` or `null` | | `payment_provider_transaction_id` | `string` or `null` | | `amount_attempted` | `string` or `null` | | `currency` | `string` or `null` | | `outcome` | `"succeeded"`, `"failed"`, or `"skipped"` | | `failure_reason` | `string` or `null` | During the migration from Orb's legacy dunning system, accounts that previously consumed the legacy `invoice.dunning_schedule_*` webhooks also receive those events alongside Collections Automations activity, for a deprecation window. See [Migrating from legacy dunning webhooks](/financial-operations/automations#migrating-from-legacy-dunning-webhooks). ### invoice.edited Issued when a draft invoice has been edited via the webapp. The `previous_attributes` object will be present and populated with the previous state of any `Invoice` attributes that have changed. | key | schema | | ------------------------------------------- | --------------------------------------------------------------------- | | `invoice` | [Invoice](/api-reference/invoice/fetch-invoice) | | `properties.previous_attributes.amount_due` | `string` | | `properties.previous_attributes.subtotal` | `string` | | `properties.previous_attributes.total` | `string` | | `properties.previous_attributes.discounts` | `array` of Discounts | | `properties.previous_attributes.minimum` | [Minimum]() | | `properties.previous_attributes.line_items` | `array` of [Invoice Line Items](/api-reference/invoice/fetch-invoice) | | `properties.previous_attributes.net_terms` | `int` | | `properties.previous_attributes.due_date` | `date-time` | ### invoice.invoice\_date\_elapsed Serializing and consuming this webhook can be very expensive in the presence of a large number of subscriptions and is not enabled by default. Please reach out to the Orb team to opt-in to the webhook or discuss the intended workflow. Issued when the `invoice_date` of an invoice has elapsed. | key | schema | | ------------------------- | ----------------------------------------------- | | `invoice` | [Invoice](/api-reference/invoice/fetch-invoice) | | `properties.invoice_date` | `date-time` | ### invoice.issue\_failed Issued when an invoice fails to issue. | key | schema | | ------------------- | ----------------------------------------------- | | `invoice` | [Invoice](/api-reference/invoice/fetch-invoice) | | `properties.reason` | `string` | ### invoice.issued Issued when an invoice transitions to the `"issued"` state. `"issued"` invoices are frozen (they cannot be edited, manually or via the API). The `automatically_marked_as_paid` property will be true when the issued invoice's amount due is zero, which results in the invoice status being set to paid. | key | schema | | ----------------------------------------- | ----------------------------------------------- | | `invoice` | [Invoice](/api-reference/invoice/fetch-invoice) | | `properties.automatically_marked_as_paid` | `boolean` | ### invoice.manually\_marked\_as\_paid Issued when an invoice is manually marked as paid. | key | schema | | ---------------------------------- | ----------------------------------------------- | | `invoice` | [Invoice](/api-reference/invoice/fetch-invoice) | | `properties.payment_received_date` | `date-time` | | `properties.external_id` | `string` | | `properties.notes` | `string` | ### invoice.manually\_marked\_as\_void Issued when an invoice is manually marked as void. | key | schema | | ------------ | ----------------------------------------------- | | `invoice` | [Invoice](/api-reference/invoice/fetch-invoice) | | `properties` | `{}` | ### invoice.payment\_failed Issued when automated payment collection for an invoice fails for a configured payment gateway. | key | schema | | -------------------------------------------- | ----------------------------------------------- | | `invoice` | [Invoice](/api-reference/invoice/fetch-invoice) | | `properties.payment_provider` | `"stripe"` | | `properties.payment_provider_id` | `string` | | `properties.payment_provider_transaction_id` | `string` or `null` | ### invoice.payment\_processing Issued when a payment for an invoice starts processing. | key | schema | | -------------------------------- | ----------------------------------------------- | | `invoice` | [Invoice](/api-reference/invoice/fetch-invoice) | | `properties.payment_provider` | `"stripe"` | | `properties.payment_provider_id` | `string` | ### invoice.payment\_succeeded Issued when automated payment collection for an invoice succeeds for a configured payment gateway. This webhook is not sent for \$0 invoices that are automatically marked as paid upon issuance. | key | schema | | -------------------------------------------- | ----------------------------------------------- | | `invoice` | [Invoice](/api-reference/invoice/fetch-invoice) | | `properties.payment_provider` | `"stripe"` | | `properties.payment_provider_id` | `string` | | `properties.payment_provider_transaction_id` | `string` | ### invoice.sync\_failed Issued when an invoice fails to sync to the invoice provider. | key | schema | | -------------------------------- | ----------------------------------------------- | | `invoice` | [Invoice](/api-reference/invoice/fetch-invoice) | | `properties.payment_provider` | `string` | | `properties.payment_provider_id` | `string` | ### invoice.sync\_succeeded Issued when an invoice successfully syncs to the invoice provider. | key | schema | | -------------------------------- | ----------------------------------------------- | | `invoice` | [Invoice](/api-reference/invoice/fetch-invoice) | | `properties.payment_provider` | `string` | | `properties.payment_provider_id` | `string` | ### invoice.undo\_mark\_as\_paid Issued when undoing the `Paid` status for an invoice that was manually marked as paid. | key | schema | | ------------ | ----------------------------------------------- | | `invoice` | [Invoice](/api-reference/invoice/fetch-invoice) | | `properties` | `{}` | ### plan.default\_version\_set Issued when a plan's default version is set. | key | schema | | -------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `plan` | Object with `id` (`string`), `external_plan_id` (`string` or `null`), and `name` (`string`). See [Plan](/api-reference/plan/fetch-plan). | | `properties.previous_default_version_number` | `number` | | `properties.new_default_version_number` | `number` | ### plan.version\_created Issued when a new plan version is created. | key | schema | | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `plan` | Object with `id` (`string`), `external_plan_id` (`string` or `null`), and `name` (`string`). See [Plan](/api-reference/plan/fetch-plan). | | `properties.plan_version_number` | `number` | | `properties.plan_version_description` | `string` | ### price.edited Issued when a price is edited. This webhook is only issued when metadata for the resource changes, as prices are otherwise immutable. The `previous_attributes` object will be present and populated with the previous state of any price attributes that have changed. | key | schema | | ----------------------------------------- | ----------------------------------------- | | `price` | [Price](/api-reference/price/fetch-price) | | `properties.previous_attributes.metadata` | `object` | ### resource\_event.test Issued via the Orb UI to test if your webhook configuration is set up properly and ready to receive events from us. | key | schema | | --------- | -------- | | `message` | `string` | ### subscription.cancellation\_scheduled Issued when a subscription cancellation is scheduled for the future. | key | schema | | ------------------------------ | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `properties.cancellation_date` | `date-time` | ### subscription.cancellation\_unscheduled Issued when a previously scheduled subscription cancellation is unscheduled/cancelled. | key | schema | | --------------------------------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `properties.original_cancellation_date` | `date-time` | Unscheduling a cancellation is a lossy operation—the subscription may not fully match its original pre-cancellation state. ### subscription.created Issued when a subscription resource is created. | key | schema | | -------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `properties` | `{}` | ### subscription.edited Issued when a subscription has been edited. This will not be triggered for changes to pricing. | key | schema | | ----------------------------------------------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `properties.previous_attributes.metadata` | `object` | | `properties.previous_attributes.default_invoice_memo` | `string` | | `properties.previous_attributes.auto_collection` | `boolean` | | `properties.previous_attributes.net_terms` | `number` | | `properties.previous_attributes.invoicing_threshold` | `number` | ### subscription.ended Occurs whenever a customer’s subscription ends/lapses. | key | schema | | -------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `properties` | `{}` | ### subscription.fixed\_fee\_quantity\_updated Issued when a subscription's fixed fee quantity has been updated. | key | schema | | --------------------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `properties.old_quantity` | `number` | | `properties.new_quantity` | `number` | | `properties.effective_date` | `date-time` | | `properties.price_id` | `string` | ### subscription.plan\_change\_scheduled | key | schema | | :---------------------------- | :------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `properties.previous_plan_id` | `string` | | `properties.new_plan_id` | `string` | | `properties.change_date` | `string` | ### subscription.plan\_changed Issued when a scheduled plan change occurs. | key | schema | | ----------------------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `properties.previous_plan_id` | `string` | ### subscription.plan\_version\_change\_scheduled Issued when a plan version change is scheduled in the future for a subscription. | key | schema | | ----------------------------------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `properties.effective_date` | `date-time` | | `properties.previous_plan_version_number` | `number` | | `properties.new_plan_version_number` | `number` | ### subscription.plan\_version\_changed Issued when a subscription moves to a new plan version. | key | schema | | ----------------------------------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `properties.effective_date` | `date-time` | | `properties.previous_plan_version_number` | `number` | | `properties.new_plan_version_number` | `number` | ### subscription.started Issued when a subscription begins. | key | schema | | -------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `properties` | `{}` | ### subscription.trial\_ended Issued when a subscription's trial ends. | key | schema | | :------------- | :------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `properties` | `{}` | ## Webhook Event Types: Usage Patterns and Balance Events Orb currently issues the following usage patterns and balance events webhooks: ### customer.credit\_balance\_depleted Issued when a customer's prepaid credit balance is depleted. | key | schema | | -------------------------------------- | -------------------------------------------------- | | `customer` | [Customer](/api-reference/customer/fetch-customer) | | `alert_configuration` | [Alert](/api-reference/alert/fetch-alert) | | `properties.pricing_unit.name` | `string` | | `properties.pricing_unit.symbol` | `string` | | `properties.pricing_unit.display_name` | `string` | ### customer.credit\_balance\_dropped Issued when a customer's prepaid credit balance is depleted to a configured threshold. | key | schema | | -------------------------------------- | -------------------------------------------------- | | `customer` | [Customer](/api-reference/customer/fetch-customer) | | `alert_configuration` | [Alert](/api-reference/alert/fetch-alert) | | `properties.balance_threshold` | `string` | | `properties.pricing_unit.name` | `string` | | `properties.pricing_unit.symbol` | `string` | | `properties.pricing_unit.display_name` | `string` | ### customer.credit\_balance\_recovered Issued when a customer's prepaid credit balance is replenished after depletion. | key | schema | | -------------------------------------- | -------------------------------------------------- | | `customer` | [Customer](/api-reference/customer/fetch-customer) | | `alert_configuration` | [Alert](/api-reference/alert/fetch-alert) | | `properties.pricing_unit.name` | `string` | | `properties.pricing_unit.symbol` | `string` | | `properties.pricing_unit.display_name` | `string` | ### subscription.alert\_disabled Issued when Orb automatically disables an alert. Currently sent when a [grouped cost alert](/usage-alerting/grouped-cost-alerts) exceeds the per-evaluation group cardinality limit and is disabled for the affected subscription. `reason` identifies why the alert was disabled, and `cardinality` / `cardinality_limit` report the observed and maximum group counts. | key | schema | | ------------------------------ | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `alert_configuration` | [Alert](/api-reference/alert/fetch-alert) | | `properties.reason` | `string` | | `properties.cardinality` | `number` | | `properties.cardinality_limit` | `number` | ### subscription.cost\_exceeded Issued when a subscription's accrued spend for a month exceeds a pre-configured currency amount. Orb will not evaluate alerts on cost that are configured with a threshold below the total of a plan's fixed costs with a prioritized cadence during billing period rollovers. These alerts can be expected to fire near the first actual usage event received for the new billing period and will subsequently be evaluated several times a day. The evaluated spend is the running invoice total after any credit offsets, so prepurchased credits applied to the subscription reduce the spend that is compared against the threshold. | key | schema | | ----------------------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `alert_configuration` | [Alert](/api-reference/alert/fetch-alert) | | `properties.timeframe_start` | `date-time` | | `properties.timeframe_end` | `date-time` | | `properties.amount_threshold` | `number` | ### subscription.grouped\_cost\_exceeded Issued when one or more groups of a [grouped cost alert](/usage-alerting/grouped-cost-alerts) exceed a pre-configured currency amount. A single event batches every group that crossed a threshold in the same evaluation. Each entry in `groups` reports that group's `group_values` (in `grouping_keys` order), the `amount_threshold` it crossed, and its `evaluated_amount`. | key | schema | | -------------------------------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `alert_configuration` | [Alert](/api-reference/alert/fetch-alert) | | `properties.timeframe_start` | `date-time` | | `properties.timeframe_end` | `date-time` | | `properties.grouping_keys` | `array` of `string` | | `properties.groups` | `array` of objects | | `properties.groups[].group_values` | `array` of `string` | | `properties.groups[].amount_threshold` | `number` | | `properties.groups[].evaluated_amount` | `number` | ### subscription.spend\_exceeded Issued when a subscription's rated spend exceeds a pre-configured currency amount. Rated spend is the sum of the draft invoice's line item subtotals in the alert's currency, measured before credit drawdown and invoice-level adjustments. See [spend alerts](/usage-alerting/spend-alerts). | key | schema | | ----------------------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `alert_configuration` | [Alert](/api-reference/alert/fetch-alert) | | `properties.timeframe_start` | `date-time` | | `properties.timeframe_end` | `date-time` | | `properties.amount_threshold` | `number` | | `properties.evaluated_amount` | `number` | ### subscription.usage\_exceeded Issued when a billable metric in a subscription exceeds a pre-configured quantity threshold. | key | schema | | ------------------------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `alert_configuration` | [Alert](/api-reference/alert/fetch-alert) | | `properties.billable_metric_id` | `string` | | `properties.timeframe_start` | `date-time` | | `properties.timeframe_end` | `date-time` | | `properties.quantity_threshold` | `number` | ## Webhook Event Types: Accounting Sync Orb issues the following webhooks when syncing resources to external accounting systems. These events are only emitted when the Accounting Sync integration is enabled and configured. Supported connection types are "quickbooks" and "netsuite". See Accounting and QuickBooks setup guides. ### Accounting Sync Record object The `accounting_sync_record` included in these events contains the following fields: | key | schema | | ---------------------- | -------- | | `id` | `string` | | `record_type` | `string` | | `sync_action` | `string` | | `status` | `string` | | `customer_id` | `string` | | `provider_customer_id` | `string` | | `error_details` | `object` | ### credit\_block.accounting\_sync\_failed Issued when a credit block fails to sync to the configured accounting system. | key | schema | | ---------------------------- | -------------------------------------------------------- | | `block` | [Credit block](/api-reference/credit/fetch-credit-block) | | `accounting_sync_record` | `object` | | `properties.connection_type` | `string` ("quickbooks" or "netsuite") | | `properties.failure_reason` | `string` | ### credit\_block.accounting\_sync\_succeeded Issued when a credit block successfully syncs to the configured accounting system. | key | schema | | ---------------------------- | -------------------------------------------------------- | | `block` | [Credit block](/api-reference/credit/fetch-credit-block) | | `accounting_sync_record` | `object` | | `properties.connection_type` | `string` ("quickbooks" or "netsuite") | ### credit\_note.accounting\_sync\_failed Issued when a credit note fails to sync to the configured accounting system. | key | schema | | ---------------------------- | ----------------------------------------------------------- | | `credit_note` | [Credit note](/api-reference/credit-note/fetch-credit-note) | | `accounting_sync_record` | `object` | | `properties.connection_type` | `string` ("quickbooks" or "netsuite") | | `properties.failure_reason` | `string` | ### credit\_note.accounting\_sync\_succeeded Issued when a credit note successfully syncs to the configured accounting system. | key | schema | | ---------------------------- | ----------------------------------------------------------- | | `credit_note` | [Credit note](/api-reference/credit-note/fetch-credit-note) | | `accounting_sync_record` | `object` | | `properties.connection_type` | `string` ("quickbooks" or "netsuite") | ### customer.accounting\_sync\_failed Issued when a customer fails to sync to the configured accounting system. | key | schema | | ---------------------------- | -------------------------------------------------- | | `customer` | [Customer](/api-reference/customer/fetch-customer) | | `accounting_sync_record` | `object` | | `properties.connection_type` | `string` ("quickbooks" or "netsuite") | | `properties.failure_reason` | `string` | ### customer.accounting\_sync\_succeeded Issued when a customer successfully syncs to the configured accounting system. | key | schema | | ---------------------------- | -------------------------------------------------- | | `customer` | [Customer](/api-reference/customer/fetch-customer) | | `accounting_sync_record` | `object` | | `properties.connection_type` | `string` ("quickbooks" or "netsuite") | ### invoice.accounting\_sync\_failed Issued when an invoice fails to sync to the configured accounting system. | key | schema | | ---------------------------- | ----------------------------------------------- | | `invoice` | [Invoice](/api-reference/invoice/fetch-invoice) | | `accounting_sync_record` | `object` | | `properties.connection_type` | `string` ("quickbooks" or "netsuite") | | `properties.failure_reason` | `string` | ### invoice.accounting\_sync\_succeeded Issued when an invoice successfully syncs to the configured accounting system. | key | schema | | ---------------------------- | ----------------------------------------------- | | `invoice` | [Invoice](/api-reference/invoice/fetch-invoice) | | `accounting_sync_record` | `object` | | `properties.connection_type` | `string` ("quickbooks" or "netsuite") | ### subscription.accounting\_sync\_failed Issued when a subscription fails to sync to the configured accounting system. | key | schema | | ---------------------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `accounting_sync_record` | `object` | | `properties.connection_type` | `string` ("quickbooks" or "netsuite") | | `properties.failure_reason` | `string` | ### subscription.accounting\_sync\_succeeded Issued when a subscription successfully syncs to the configured accounting system. | key | schema | | ---------------------------- | -------------------------------------------------------------- | | `subscription` | [Subscription](/api-reference/subscription/fetch-subscription) | | `accounting_sync_record` | `object` | | `properties.connection_type` | `string` ("quickbooks" or "netsuite") | ### transaction.accounting\_sync\_failed Issued when a transaction fails to sync to the configured accounting system. | key | schema | | ---------------------------- | ------------------------------------- | | `transaction` | `object` | | `accounting_sync_record` | `object` | | `properties.connection_type` | `string` ("quickbooks" or "netsuite") | | `properties.failure_reason` | `string` | ### transaction.accounting\_sync\_succeeded Issued when a transaction successfully syncs to the configured accounting system. | key | schema | | ---------------------------- | ------------------------------------- | | `transaction` | `object` | | `accounting_sync_record` | `object` | | `properties.connection_type` | `string` ("quickbooks" or "netsuite") | ## Webhook Event Types: Data Exports Orb currently issues the following webhooks for data exports: ### data\_exports.transfer\_error Issued upon a failed transfer of resource data to a destination. | key | schema | | ---------------------------------- | ------------------- | | `properties.destination_name` | `string` | | `properties.description` | `string` | | `properties.resources` | `array` of `string` | | `properties.rows_transferred` | `number` | | `properties.transfer_started_at` | `date-time` | | `properties.transfer_ended_at` | `date-time` | | `properties.transfer_blamed_party` | `string` | ### data\_exports.transfer\_success Issued upon a successful transfer of resource data to a destination. | key | schema | | -------------------------------- | ------------------- | | `properties.destination_name` | `string` | | `properties.description` | `string` | | `properties.resources` | `array` of `string` | | `properties.rows_transferred` | `number` | | `properties.transfer_started_at` | `date-time` | | `properties.transfer_ended_at` | `date-time` | ## Webhooks security Each webhook event sent by Orb also includes two specific headers in the request: 1. `X-Orb-Timestamp`: This header represents the time that the webhook was sent from Orb. You can check this timestamp and compare it to a configured threshold in your system, in order to prevent processing of the webhook event. This can be a mitigation against [replay attacks](https://en.wikipedia.org/wiki/Replay_attack) where webhook events are re-sent, causing downstream systems to behave unexpectedly. 2. `X-Orb-Signature`: The signature header (formatted as `v1=`) is a security header which can be used to confirm that the webhook event originated from Orb. We highly recommend verifying the signature, and only processing events where the header matches the signature you generate. Since each webhook endpoint is associated with a secret, your backend server should re-compute the signature by preparing the payload and subsequently computing an HMAC with the SHA 256 function. * The `payload` for the HMAC is the literal `"v1:"` followed by the ISO format `X-Orb-Timestamp` and finally the literal `:` followed by the event message body itself. * You should use the configured secret for the endpoint URL as the signing secret for the HMAC. * When comparing to your generated HMAC, you can either prefix the literal `v1=` to compare with `X-Orb-Signature` header directly, or extract the element after `v1=` in `X-Orb-Signature` to check against your generated value. ### Webhooks verification Orb's SDKs provide verification methods for your webhook events, available in [Python](https://github.com/orbcorp/orb-python/blob/d5f974a033edd347555105d42b84200c12a723f3/src/orb/resources/webhooks.py#L21), [Node](https://github.com/orbcorp/orb-node/blob/bac8b816fd3aad7abc6a63ce22ea93eeba61521b/src/resources/webhooks.ts#L7), and [Go](https://github.com/orbcorp/orb-go/blob/7b7d9ac15b286914c72eef35cfbfd2c46301bf2e/webhook.go#L21). Here's an example verification snippet implemented as a Flask endpoint: ```python theme={null} from flask import Flask, request import hmac from hashlib import sha256 app = Flask(__name__) ## Example using Flask @app.route('/webhook-handler', methods=['POST']) def handle_webhook(): # parse webhook JSON webhook_event = request.json if webhook_event is None: # could not parse request return ('Unable to parse notification.', 400) expected_signature = request.headers["X-Orb-Signature"] iso_format_delivery_time = request.headers["X-Orb-Timestamp"] secret_string = "" secret = bytes(secret_string, "utf-8") # Prefix the beginning of the signature with `v1:` prefix = "v1:{0}:".format(iso_format_delivery_time) # Convert to bytes and append the body of the request message = prefix.encode("utf-8") + request.data # Generate an encode message based on the secret and the message string with sha256. hmac_object = hmac.new(secret, message, sha256) created_signature = "v1=" + hmac_object.hexdigest() if not expected_signature == created_signature: return ('Unable to verify webhook signature.', 400) # Handle based on webhook_event.type return ('Successfully processed webhook!', 200) ``` # What is Orb? Source: https://docs.withorb.com/introduction Orb is a billing platform for usage-based pricing, hybrid subscriptions, and enterprise contracts. ## The broken relay race Pricing changes should be simple: update the configuration, start billing customers. In practice, they trigger coordination cascades across Engineering, Finance, Product, and Sales. Product wants to charge based on API calls instead of seats. Engineering instruments new telemetry and deploys tracking infrastructure. Finance waits for reconciliation. Sales can't preview pricing until everything ships. What should take hours stretches across weeks. Enterprise deals make this worse. A backdated contract with prepaid credits requires special-case code, manual invoice adjustments, and careful coordination. Late data compounds the problem: when events arrive after the invoice closes, standard billing systems have already baked the usage totals into the invoice. Adding the late data would break the math. Engineering either patches the invoice manually or writes off the revenue. This is a relay race where each team waits for the next handoff. Momentum stalls at every baton pass. ## How Orb solves this The root cause is architectural. Most billing systems calculate charges as events arrive, mutating counters in real time. This locks pricing logic at ingestion time. When pricing changes, the entire pipeline must change with it. Orb takes a different approach: it stores usage events immutably and calculates bills by querying event history at billing time. Events are never modified or aggregated. Every invoice is a deterministic query over raw usage data. This changes what's possible: * **Backfills work safely.** Late events don't corrupt counters because there are no counters. Orb queries the full event history when calculating invoices. * **Pricing changes don't require re-ingestion.** Metrics are queries, not pre-aggregated values. Define a new metric and Orb calculates usage by querying historical events. No instrumentation changes, no deployment. * **Test pricing before deployment.** Run simulations against historical usage to see exact invoice previews. Model new tiers, forecast revenue impact, preview customer-specific rates before committing. * **Enterprise contracts don't require custom code.** Backdated pricing recalculates invoices by re-running queries with the new start date. Prepaid credits draw down deterministically. Custom discounts and amendments are configuration, not code. * **Complete audit trails by default.** Every charge traces to specific events and pricing rules. Drill into any line item to see which events contributed and which configuration applied. ## Revenue Design When billing is a deterministic query, pricing becomes cross-functional product work instead of a coordination problem. This is what we call Revenue Design. The broken relay race disappears because the architecture eliminates the handoffs. Product, Finance, Sales, and Engineering work in parallel instead of sequence. Pricing changes become configuration updates instead of multi-week projects. Query-based billing makes this possible. Streaming aggregation makes it impossible. ## Next steps **Start building:** [Quickstart guide](/quickstart-guide) walks through your first billing workflow—ingest events, create metrics, configure pricing, generate invoices. **Understand the architecture:** [How Orb works](/how-orb-works) explains query-based vs. streaming billing in technical detail. **Learn the data model:** [Core concepts](/core-concepts) covers Events, Metrics, Plans, Subscriptions, and Invoices. # Adjust draft invoices Source: https://docs.withorb.com/invoicing/Invoice-adjustments To accommodate cases where an invoice needs to be edited manually, Orb has a powerful adjustments workflow. Adjustments on a draft invoice can be used to add additional fixed fees, change discounts/minimums, and edit existing line items. You can use adjustments on an Orb invoice to: 1. Capture a one-off charge that’s not part of a recurring contract. For example, this may be required to capture support hours that need to be billed adhoc. 2. Increase the discount on the invoice to reflect a mistake in how usage was reported to Orb, or because of an out-of-band conversation with your customer. 3. Remove a contracted minimum to reflect a delayed integration cycle where the customer isn’t fully onboarded onto your platform. 4. Add a negative line item to easily give a customer credit or correct a mistake on the invoice. Edit pro cloud 2 Once an invoice has been adjusted, the line items, subtotal, and total are immediately updated to reflect the change. By default, invoice adjustments are visible your users in the invoice portal as well as the internal view of the invoice. Note that there are cases where invoice adjustments may not persist in light of manual actions taken. Manual edits to an upcoming draft invoice are often not retained when: * **Subscription is cancelled**: Orb generates a *new* invoice rather than modifying the existing one since the invoice may now only contain a subset of the line items (e.g. no in-advance fee for the upcoming billing period). * **Subscription is edited**: Changes to subscription pricing, such as adding or removing prices, can trigger regeneration of the draft invoice. * **Plan is migrated to a new version**: When a subscription moves to a new plan version, the draft invoice is typically regenerated to reflect the new plan structure. In all these cases, the draft invoice regeneration process does not carry over any manual adjustments that were previously applied, and these edits will need to be reapplied if still relevant. # Credit notes Source: https://docs.withorb.com/invoicing/credit-notes ## Credit issued invoices A credit note is a document that can be used to decrease the amount due or issue a credit for an already issued invoice. Common situations for issuing a credit note include: * Duplicate - a duplicate invoice was sent to a customer for the same goods or services. * Fraudulent - a customer is disputing an erroneous invoice’s charges. * Order change - the number of items charged to the customer needs to be changed. * Unsatisfactory product - the customer is requesting a refund due to a product issue. Credit notes can be applied to invoices that either have an `issued` status or a `paid` status. When a credit note is issued for an `issued` invoice, it is considered an `adjustment`. When a credit note is issued for a `paid` invoice, it is considered a `refund`. **Type is automatically determined.** You don't choose whether a credit note is an "adjustment" or "refund"—Orb determines this based on the invoice status at the moment you create the credit note. The naming refers to how the credit is applied, not your intent: "adjustment" credit notes reduce the invoice's amount due directly, while "refund" credit notes add to the customer's balance (since the invoice has already been collected). Credit notes cannot be applied to any invoices that have been synced to an external provider such as Stripe or QuickBooks. Any adjustments made to the invoice should be done with the external provider. There may be cases where credit notes are automatically added to `synced` invoices (typically as the result of a subscription change action). In these cases, the `synced` invoice is treated as a paid invoice, and the credit note is always treated as a `refund` credit note (thereby adding to the customer account balance). Credit notes will also include any applicable tax when associated with an invoice that included tax. Tax will be calculated when the credit note is issued. ### Service period configuration When creating credit notes via the API or dashboard, you can configure custom service periods that determine which portion of the original invoice's service period is being credited. Orb supports multiple modes for service period configuration: * Original service period (Default): Use the original invoice line item service periods. This is the default behavior when no custom service periods are specified. * Issuance date: Set the service period to start from the credit note issuance date, useful for partial credits from a specific point in time. * Custom: Specify a custom `start_date` and `end_date` at the credit note level. These dates will be applied uniformly to all line items in the credit note. * Custom per line item: Specify custom `start_date` and `end_date` for each individual line item. When using this mode, all line items must have their own service periods specified. #### Service period constraints * Date boundaries: Credit note service periods cannot extend beyond the service periods of the original invoice line items, and/or today's date (issuance date). * Date format: Use "YYYY-MM-DD" format (e.g., "2023-09-22") for consistency with other Orb APIs. * Inclusive dates: Both start and end dates are inclusive, covering the complete days specified * No mixing: You cannot combine global and individual service periods in the same request Service period configuration is available both in the Orb dashboard and via the API. In the dashboard, use the "Service period" dropdown when creating a credit note to select from the available options. For API integration, use the `start_date` and `end_date` parameters as described in the [API reference](/api-reference/credit-note/create-credit-note). ### Issuing an adjustment credit note To create a credit note, navigate to an `issued` invoice in the Orb dashboard. In the top right dropdown menu, select “Issue credit note”. image.png * Choose a reason for the credit note and, optionally, add a memo to your credit note. * Adjust the creditable amount for each line item in the invoice, and unselect line items that should not be credited. * The credit note will be populated with the current, maximum creditable amounts for each line item. * You can configure the service period using the "Service period" dropdown to determine which portion of the original service period to credit. * If a minimum amount was applied to the invoice or a line item, the outstanding amount can also be credited back. * The "Adjusted amount due" will reflect the new amount due on the invoice after issuance. image.png Upon issuing the credit note, the invoice’s amount due will be adjusted and the credit note’s amounts will be shown on the invoice page. The invoice PDF will also be regenerated to include the credit note. image.png ### Issuing a refund credit note When a credit note is issued for a `paid` invoice, the flow is similar to issuing a credit note for an `issued` invoice with a few differences. When the credit note is issued, the amount due on the invoice will not be adjusted based on the credit note’s value, as the amount has already been collected. Instead, the customer balance will be incremented by the value of the credit note, and the credit note will linked on the invoice page. The credit note will not be attached to the invoice’s PDF in this instance. image.png **Recommended refund workflow for payment provider-initiated refunds** * To actually return money back to a customer, you should **initiate a refund directly in your payment provider** (such as Adyen or Stripe).  * To keep accounts receivable (AR), cash, and billings reporting aligned, it’s **best practice to create a **[**refund credit note**](https://docs.withorb.com/invoicing/credit-notes#issuing-a-refund-credit-note)** in Orb and then manually decrease the customer balance in Orb** to reflect that the cash refund already occurred outside Orb. ### Adding multiple credit notes Multiple credit notes can be applied to an invoice up to the total value of the invoice. After one credit note is applied to an invoice, the issue credit flow will populate the maximum creditable amounts for each line item. image.png All applied credit notes will appear on the invoice page. image.png ### Voiding a credit note To void a credit note, click on the credit note in the top left of the invoice page. From the dropdown at the top right of the page, click “Mark as void”. image.png After the confirmation modal, the credit note will now be marked as void. For adjustment credit notes, the invoice’s amount due will be changed to reflect the credit note’s value being removed. image.png Refund credit notes cannot be voided. ### Credit notes and customer balance An invoice’s amount due can be adjusted by either utilizing the customer balance or issuing a credit note. Whereas the customer balance is automatically applied at the invoice’s finalization by Orb, a credit note can be manually applied to an already-issued invoice. Further, credit notes can be applied to invoices that have already utilized the customer balance in order to further affect the amount due. Applying adjustment credit notes will revert any applied balance, apply the credit note’s amount, and then re-apply any applicable remaining balance. The same behavior occurs if the credit note is voided. For example, if an invoice has a customer balance adjustment of \$5: | | | | ------------------ | ------- | | Invoice total | \$10.00 | | Balance adjustment | -\$5.00 | | Amount due | \$5.00 | When a \$3 credit note is applied, the customer balance adjustment will be removed from the invoice and the accompanying amount will be incremented on the customer’s balance (e.g. \$5 will be returned to the balance). The credit note’s amount is then applied and then a maximum of \$5 can be reapplied to the invoice. The resulting invoice’s amount due will be broken down as: | | | | ------------------ | ------- | | Invoice total | \$10.00 | | Credit note amount | -\$3.00 | | Balance adjustment | -\$5.00 | | Amount due | \$2.00 | If the credit note is voided, the \$3 balance adjustment is reverted, the credit note’s amount is removed from the invoice, and the maximum of \$5 can be re-applied to the invoice. | | | | ------------------ | ------- | | Invoice total | \$10.00 | | Balance adjustment | -\$5.00 | | Amount due | \$5.00 | Issuing refund credit notes will not affect the customer balance transactions on an invoice. ### Credit notes and subscription cancellation When cancelling a subscription during a billing period, Orb will issue a credit note against the original invoice for fees paid in-advance. The credit note will be for the remainder of the fee's period that is no longer being used. Unpaid invoices's total amount due will be reduced. If the relevant invoice has already been paid, the prorated amount is added to the customer's balance via the credit note and can be consumed by future invoices. ## Finance worked examples ### Example 1: Adjustment credit note with tax A customer disputes a \$500 charge on an issued invoice. The original invoice included 10% tax. **Original invoice:** | Line item | Amount | | ------------ | ------------ | | Platform fee | \$500.00 | | Tax (10%) | \$50.00 | | **Total** | **\$550.00** | **Credit note issued for \$200 of the platform fee:** | Line item | Amount | | --------------------- | ------------- | | Platform fee credit | -\$200.00 | | Tax adjustment (10%) | -\$20.00 | | **Credit note total** | **-\$220.00** | **Adjusted invoice:** | Line item | Amount | | -------------- | ------------ | | Platform fee | \$300.00 | | Tax (10%) | \$30.00 | | **Amount due** | **\$330.00** | Tax is automatically recalculated proportionally when issuing credit notes. ### Example 2: Mid-period cancellation refund A customer on a \$900/month plan (billed in-advance) cancels on March 10th. They've already paid for the full month of March. **Calculation:** * Days in March: 31 * Days used (March 1-10): 10 * Days unused (March 11-31): 21 * Refund amount: \$900 × (21/31) = \$609.68 **Credit note:** | Line item | Amount | | ----------------------------- | ------------- | | Platform fee (unused portion) | -\$609.68 | | **Credit note total** | **-\$609.68** | Since the invoice was already paid, this is a **refund** credit note. The \$609.68 is added to the customer's balance and can be: * Applied to future invoices * Refunded via your payment provider ### Example 3: Impact on revenue recognition Credit notes affect revenue recognition based on the service period credited: **Original invoice (March platform fee):** * Amount: \$3,000 * Service period: March 1 - March 31 * Daily recognition: \$3,000 ÷ 31 = \$96.77/day **Credit note issued March 15 for the remaining period:** * Credit amount: \$1,548.39 (16 days × \$96.77) * Service period on credit note: March 16 - March 31 **Revenue recognition impact:** | Period | Originally scheduled | After credit note | | --------------- | -------------------- | ---------------------- | | March 1-15 | \$1,451.61 | \$1,451.61 (unchanged) | | March 16-31 | \$1,548.39 | \$0 (reversed) | | **Total March** | **\$3,000.00** | **\$1,451.61** | The credit note reverses revenue recognition for the credited service period. If the accounting period is closed, the reversal appears as a catch-up adjustment in the next open period. ### Email delivery If the account and customer are configured for email delivery, all credit notes will be delivered on creation. Adjustment credit notes will include two PDFs: one of the credit note and one for the updated invoice with the credit applied. Refund credit notes will only have the credit note's PDF. Voiding a credit note will not send any emails. # Cumulative invoicing Source: https://docs.withorb.com/invoicing/cumulative-invoicing **This feature is in beta.** Cumulative invoicing is currently available to a limited set of customers. If you're interested in enabling this feature or have feedback, please reach out to your Orb account team or [contact support](https://support.withorb.com/). ## Overview Cumulative invoicing lets you **evaluate pricing over a longer billing period while invoicing your customers on a shorter cadence**. The classic example is an annual tiered usage that you still want to invoice monthly: tiers accumulate over the full year, but the customer gets an invoice every month for just that month's charges. Under the hood, cumulative invoicing separates two concepts that are often conflated: * **Billing cadence** - how Orb evaluates the price (e.g. tiers over 1 year). * **Invoicing cadence** - how often Orb cuts an invoice (e.g. monthly). Each invoice in a cumulative series reflects usage **to date** within the billing period. Orb computes the amount owed for that period and subtracts what has already been invoiced, so every invoice shows only the incremental amount due. ## Is Cumulative Invoicing right for you? Use cumulative invoicing if: * You offer a multi-period tiered usage contract but want to invoice on a shorter cadence (annual tiers with monthly invoicing is the most common setup, but any combination works - e.g. 6-month tiers with bi-monthly invoicing, or quarterly tiers with monthly invoicing). * Tiers should be evaluated against year-to-date usage rather than resetting each invoicing period. * You want each invoice to show only that period's charges, while still being driven by the full billing period's pricing. Skip cumulative invoicing if: * You want to bill when usage crosses a specific trigger. Use [threshold invoicing ](https://docs.withorb.com/invoicing/threshold-invoicing#threshold-invoicing)instead - which triggers an invoice when total usage-based charges reach a configured monetary threshold, whereas cumulative invoicing is purely time-based. * Your billing and invoicing cadences are the same (standard in-arrears billing covers this). ## How Cumulative Invoicing works ### **1. Billing cadence vs. invoicing cadence** On a cumulative price, you configure two separate cadences: * billing\_cycle\_configuration controls the period over which pricing is evaluated (e.g. 1 year). * invoicing\_cycle\_configuration controls when invoices are cut (e.g. 1 month). Orb computes cumulative usage over the billing period, applies tiers to that cumulative quantity, and then on each invoicing cycle calculates how much is owed now by subtracting what's already been invoiced. **Scenario:** You have a customer on a plan with: * Billing cadence: **Annual** * Invoicing cadence: **Monthly** * Tiered pricing on Output\_Tokens: * 0 – 100 units: \$1.00/unit * 100+ units: \$0.50/unit Usage pattern over the first two months of the year: * Month 1 (Jan 2026): 3,799 units * Month 2 (Feb 2026): 1,920 units (cumulative: 5,719) ### **Without Cumulative Invoicing** Without cumulative invoicing, each month stands alone and tiers reset: * **Month 1 invoice:** 100 × \$1.00 + 3,699 × \$0.50 = **\$1,949.50** * **Month 2 invoice:** 100 × \$1.00 + 1,820 × \$0.50 = **\$1,010.00** (resets into tier 1 again) Total after two months: **\$2,959.50**. The customer pays the tier 1 rate on the first 100 units *every* month, so the tier discount only kicks in within a single month's usage. ### **With Cumulative Invoicing** * **Month 1 invoice:** cumulative 3,799 units → 100 × \$1.00 + 3,699 × \$0.50 = \$1,949.50 total → **\$1,949.50 this month** * **Month 2 invoice:** cumulative 5,719 units → 100 × \$1.00 + 5,619 × \$0.50 = \$2,909.50 total, minus \$1,949.50 already invoiced → **\$960.00 this month** Total after two months: **\$2,909.50**. The customer's usage is priced against the annual tier schedule, the first 100 units are charged at tier 1 exactly once, but they still get a clean monthly invoice each cycle. In this example cumulative invoicing saves the customer \$50 vs. resetting tiers monthly (the 100 tier-1 units in Month 2 drop into tier 2 at \$0.50 instead of \$1.00). ### **2. Setting up Cumulative Invoicing** Cumulative invoicing is configured at the price level by setting a shorter invoicing cycle configuration than the billing cycle configuration. **In the Orb app** When creating or editing a price, set the **Billing cycle** to the period over which tiers should be evaluated (e.g. Annual). Then check **Invoice at a higher frequency** and choose a shorter cadence (e.g. 1 month). Image **Via the API** When creating or updating prices programmatically (i.e. [creating a plan](https://docs.withorb.com/api-reference/plan/create-plan), [updating plan version](https://docs.withorb.com/api-reference/plan/create-plan-version), or[ creating a price](https://docs.withorb.com/api-reference/price/create-price#create-price) directly), set billing\_cycle\_configuration and invoicing\_cycle\_configuration to different values on the price. ### **3. Invoice Display** Cumulative invoices are designed to be both **incremental** (what changed since last invoice) and **contextual** (how we got to the current tier/amount): * **The usage breakdown view** surfaces the current period’s usage, so it is clear what the usage during this billing period was. **Example usage graphs**

January Invoice

January Invoice

February Invoice

February Invoice
* **Previous invoices & Amount due** under the same cumulative price are listed for context, so it's easy to see how charges have built up over time. Furthermore, the amount due shows the current invoice's charges, not the entire year-to-date amount. So the number at the bottom of each invoice is the amount owed for that invoicing period.

January Invoice

January Invoice

February Invoice

February Invoice
## Viewing usage by invoicing period (API) Cumulative invoices use the standard invoice APIs, there is no separate resource. For cumulative pricing specifically, Orb exposes a dedicated endpoint that returns usage broken down by each invoicing period within the billing cycle: ```bash theme={null} curl --location '[https://api.withorb.com/v1/invoices/{invoice\_id}/usage\_breakdown](https://api.withorb.com/v1/invoices/{invoice_id}/usage_breakdown)' \\ --header 'Authorization: Bearer {API\_KEY}' ``` Continuing the scenario above, calling usage\_breakdown on the Month 2 invoice returns Month 1 and Month 2 as two periods. ```json theme={null} { "data": [ { "line_item_id": "9J6ULs5PjMtiBZik", "name": "Output_Tokens", "price_id": "i8CRqhSEQ74aYyV5", "periods": [ { "invoice_id": "Y9nXmEXoDSqVfs4q", "invoice_number": "HPHNTY-0001", "start_date": "2026-01-01T00:00:00+00:00", "end_date": "2026-02-01T00:00:00+00:00", "quantity": 3799.0, "amount": "1949.50", "subtotal": "1949.50", "sub_line_items": [ { "type": "tier", "name": "0-100 units", "quantity": 100.0, "amount": "100.00", "tier_config": { "first_unit": 0.0, "last_unit": 100.0, "unit_amount": "1.00" } }, { "type": "tier", "name": "100+ units", "quantity": 3699.0, "amount": "1849.50", "tier_config": { "first_unit": 100.0, "last_unit": null, "unit_amount": "0.50" } } ] }, { "invoice_id": "8ZxgkYrXRt46NVUL", "invoice_number": "HPHNTY-0002", "start_date": "2026-02-01T00:00:00+00:00", "end_date": "2026-03-01T00:00:00+00:00", "quantity": 1920.0, "amount": "960.00", "subtotal": "960.00", "sub_line_items": [ { "type": "tier", "name": "0-100 units", "quantity": 0.0, "amount": "0.00", "tier_config": { "first_unit": 0.0, "last_unit": 100.0, "unit_amount": "1.00" } }, { "type": "tier", "name": "100+ units", "quantity": 1920.0, "amount": "960.00", "tier_config": { "first_unit": 100.0, "last_unit": null, "unit_amount": "0.50" } } ] } ] } ] } ``` Each element in periods represents one invoicing cycle contributing to the billing cycle. The response covers the current invoice and every prior invoicing period in the same billing cycle, ordered chronologically. For non-cumulative invoices, each line item is returned as a single period, so the endpoint is safe to call generally. A few fields worth calling out on each period: * `start_date, end_date` - The service period for **that invoicing cycle**, not the full billing cycle. * `quantity` - Usage scoped to **that invoicing cycle**, not the cumulative total across the billing period. * `sub_line_items` - For tier, matrix, and similar price types, shows how that period's units were distributed across tiers based on the cumulative usage at the time each unit was consumed. Empty for simpler price models. Same shape as sub\_line\_items on the [Fetch invoice API](https://docs.withorb.com/api-reference/invoice/fetch-invoice#response-line-items-items-sub-line-items). ## Syncing Cumulative Invoices to external systems When you sync cumulative invoices to systems like NetSuite, Stripe, or QuickBooks, Orb sends the **scoped period amounts**, the incremental amount for each invoicing period, rather than the full year-to-date total. This keeps service periods and recognized revenue aligned with each invoice. Over the course of the full billing period, the sum of synced invoices still equals the total cumulative charges for the period. The practical upshot: * Your accounting system sees clean per-period (e.g. monthly) revenue. * Your teams can still rely on Orb's UI and API to understand the cumulative usage and tiers driving those charges. # Customer balance Source: https://docs.withorb.com/invoicing/customer-balance-transactions Customer balance in Orb functions like a **"digital wallet"** that tracks accounts receivable amounts in Orb. **Not to be confused with prepaid credits.** Customer balance is separate from the [prepaid credit ledger](/product-catalog/prepurchase). See [Credit systems](/product-catalog/credit-systems) for a detailed comparison of when to use each. * It is always maintained in the customer’s billing currency. * It is automatically applied to the subsequent invoice upon issuance, and modifies the final amount due.  * **Customer balance does not affect revenue recognition.** Revenue in Orb continues to be recognized based on invoice line items, based on actual accrual + delivery of performance obligations. * **Customer balance does not create deferred revenue**. It is strictly an accounts receivable (AR) adjustment in Orb, and is not associated with any standalone billing event or payment. * However, it **does** impact [accounts receivable (AR)](https://docs.withorb.com/financial-operations/ar-aging), collections, and cash/payment reporting, since it represents real outstanding value owed between you and your customer. Customerbalance Pn ### How customer balance is modified 1. **Manual adjustment** 1. You can directly increase or decrease a customer’s balance.  2. Manual increases can result from a “good will” account credit, or concessions to a customer for unsatisfactory service.  3. Manual decreases are typically used for corrections / reconciliation. 2. [**Orb facilitated refunds**](https://docs.withorb.com/invoicing/credit-notes#issuing-a-refund-credit-note) 1. When a credit note is applied to a paid invoice, or that paid invoice is voided, Orb  increases the customer balance to represent the refunded value.  3. [**Small Balance Carryovers**](https://docs.withorb.com/invoicing/payments#automatic-balance-carryover-for-small-invoices) 1. Payment providers like Adyen and Stripe require a minimum charge amount of \$0.50 (or current equivalent). 2. If an invoice is issued with an amount below this threshold, it is automatically marked as paid and the small amount is carried over to the customer balance. This amount is then applied to the subsequent invoice, and increases the final amount due.  **Recommended refund workflow for payment provider-initiated refunds** * To actually return money back to a customer, you should **initiate a refund directly in your payment provider** (such as Adyen or Stripe).  * To keep accounts receivable (AR), cash, and billings reporting aligned, it’s ******best practice to create a ******[******refund credit note******](https://docs.withorb.com/invoicing/credit-notes#issuing-a-refund-credit-note)****** in Orb and then manually decrease the customer balance in Orb****** to reflect that the cash refund already occurred outside Orb. ### Accounting impact (T-accounts) This is how customer balance impacts the ledger for the following example: 1. Invoice A (\$100) issued. 2. Invoice A (\$100) is paid. 3. Credit note A (\$20) is issued. 4. Invoice B (\$50) issued. 5. Invoice B (\$50) paid. Cbttaccounts # Invoicing Overview Source: https://docs.withorb.com/invoicing/introduction In addition to Orb's billing functionality, Orb also features a fully native invoicing solution. Orb Invoicing allows you to run the entirety of your revenue operations workflow without relying on an external system for collections. It serves as a replacement for tools like Stripe Invoicing or Bill.com, and provides invoices with richer detail and transparency. Orb Invoicing is also the basis for Orb’s reporting functionality, allowing you to trace from a single usage event to a dollar that your business receives. Orb Invoicing is built on a few key principles: 1. **Accuracy first**: Invoices are directly integrated with the underlying billing engine, ensuring that the your invoice reflects your plan structure and line items are derived from your event data. The ability to adjust invoices ensures that Orb remains the source of truth for the invoices your customers receive. 2. **Transparency by default**: By default, the invoice Orb issues to your customers is as clear as what you see internally. Orb’s invoices show rich graphs of day-by-day utilization, not simply a series of static line items. This visibility decreases surprises for your customer-facing teams, and helps your customers understand how they’re being charged for your product. 3. **Clear, not clever**: Hybrid billing models can be sophisticated — often featuring prices with different cadences, variable usage over time, levers like discounts and minimums, and even pre-purchase drawdowns. Throughout the product, Orb ensures that this information is displayed in a digestible format. Orb provides the ability to integrate with other invoicing providers. To setup an invoicing integration view the docs [here](/integrations-and-exports/introduction). Orb invoicing overview # Invoice calculation examples Source: https://docs.withorb.com/invoicing/invoice-calculation-examples This guide provides detailed worked examples showing how Orb calculates invoice amounts through each step of the calculation pipeline. These examples demonstrate the interplay between usage, pricing models, adjustments, and credits. ## The calculation pipeline Every line item on an invoice passes through these seven steps in order: | Step | Name | Description | | ---- | --------------- | --------------------------------------------------------------------------------------- | | 1 | **Quantity** | Evaluate billable metrics | | 2 | **Subtotal** | Apply pricing function (quantity × rate) | | 3 | **Adjustments** | Apply in order: Usage discount → Amount discount → Percent discount → Minimum → Maximum | | 4 | **Prepurchase** | Deduct prepaid credits | | 5 | **Conversion** | Convert virtual to real currency | | 6 | **Partial** | Subtract previously invoiced amounts | | 7 | **Tax** | Calculate and add tax | ## Example 1: Usage-based pricing with tiered rates A SaaS company charges for API calls using tiered pricing: | Tier | Range | Price per call | | ---- | ---------------- | -------------- | | 1 | 0 - 10,000 | \$0.001 | | 2 | 10,001 - 100,000 | \$0.0008 | | 3 | 100,001+ | \$0.0005 | **Customer usage**: 150,000 API calls this month **Step-by-step calculation**: ``` Step 1: Quantity Total API calls = 150,000 Step 2: Subtotal (Tiered pricing) Tier 1: 10,000 calls × $0.001 = $10.00 Tier 2: 90,000 calls × $0.0008 = $72.00 Tier 3: 50,000 calls × $0.0005 = $25.00 ───────────────────────────────────────── Subtotal = $107.00 Step 3: No adjustments applied Step 4: No prepaid credits Step 5: Already in USD (no conversion) Step 6: No previous partial invoices Step 7: Tax (8%) $107.00 × 0.08 = $8.56 ───────────────────────────────────────── Total with tax = $115.56 ``` ## Example 2: Multiple adjustments in order A customer has a plan with: * Usage-based compute pricing: \$0.10 per compute hour * 10% percentage discount * \$50 minimum charge * \$500 maximum charge **Customer usage**: 200 compute hours **Step-by-step calculation**: ``` Step 1: Quantity Compute hours = 200 Step 2: Subtotal 200 hours × $0.10 = $20.00 Step 3: Adjustments (in order) 3a. Usage discount: None Current: $20.00 3b. Amount discount: None Current: $20.00 3c. Percent discount: 10% $20.00 × 0.10 = $2.00 discount Current: $20.00 - $2.00 = $18.00 3d. Minimum: $50 $18.00 < $50, apply minimum Delta: $50.00 - $18.00 = +$32.00 Current: $18.00 + $32.00 = $50.00 3e. Maximum: $500 $50.00 < $500, no change ───────────────────────────────────── After adjustments = $50.00 Step 4-7: No credits, conversion, or partial invoices Tax (10%): $50.00 × 0.10 = $5.00 ───────────────────────────────────────── Total with tax = $55.00 ``` The order of adjustments matters significantly. Percentage discounts are applied before minimums, which means a discount can reduce the subtotal below the minimum, and then the minimum kicks in. This is intentional—it ensures customers always pay at least the minimum commitment. ## Example 3: Prepaid credits with minimum charges A customer has: * \$500 prepaid credits * Usage-based charges totaling \$300 * \$400 minimum commitment **The key insight**: Minimums are applied *before* prepaid credits are deducted. **Step-by-step calculation**: ``` Step 2: Subtotal Usage charges = $300.00 Step 3: Adjustments Minimum check: $300 < $400 Apply minimum delta = +$100.00 ───────────────────────────────────────── After minimum = $400.00 Step 4: Prepurchase (prepaid credits) Available credits: $500 Amount to deduct: $400 Credits used = $400.00 ───────────────────────────────────────── After prepurchase = $0.00 Remaining credits = $100.00 Step 7: Tax (10%) Tax on $0.00 = $0.00 ───────────────────────────────────────── Amount due = $0.00 ``` **Why this order?** If prepaid credits were applied before the minimum, customers could avoid contractual minimums by maintaining a credit balance. By applying the minimum first, the commitment is enforced, and then credits reduce the actual payment. ## Example 4: Multi-line item with invoice-level adjustments A customer has two prices on their plan: * **Compute**: \$0.10/hour, 1,000 hours used = \$100 * **Storage**: \$0.05/GB, 500 GB used = \$25 * **Invoice-level discount**: \$20 off (applies to both) **How the discount is distributed**: ``` Total subtotal: $100 + $25 = $125 Distribution formula: Price A share = (Price A subtotal / Total subtotal) × Discount Compute discount: ($100 / $125) × $20 = $16.00 Storage discount: ($25 / $125) × $20 = $4.00 Final line items: Compute: $100.00 - $16.00 = $84.00 Storage: $25.00 - $4.00 = $21.00 ───────────────────────────────────────── Invoice subtotal = $105.00 ``` Invoice-level adjustments are distributed proportionally based on each line item's share of the total subtotal. This ensures the adjustment is fairly allocated across all applicable prices. ## Example 5: Virtual currency with conversion A customer uses "Compute Credits" (virtual currency) with: * Conversion rate: 1 credit = \$0.50 USD * 1,000 credits prepaid * 1,500 credits used this period **Step-by-step calculation**: ``` Step 2: Subtotal (in virtual currency) Usage: 1,500 credits Step 4: Prepurchase Available: 1,000 credits Deducted: 1,000 credits ───────────────────────────────────────── Overage: 500 credits Step 5: Conversion (virtual to real currency) 500 credits × $0.50/credit = $250.00 ───────────────────────────────────────── After conversion = $250.00 Step 7: Tax (10%) $250.00 × 0.10 = $25.00 ───────────────────────────────────────── Total with tax = $275.00 ``` ## Example 6: Threshold billing (partial invoices) Threshold billing issues an invoice when accumulated charges exceed a threshold. Here's how subsequent invoices handle previously invoiced amounts. **Setup**: * Threshold: \$500 * First invoice issued at \$520 (charges accumulated to that point) * End of period: total charges are \$800 **End-of-period invoice calculation**: ``` Step 2: Subtotal Total period charges = $800.00 Steps 3-5: Adjustments, credits, conversion (Assuming none for simplicity) = $800.00 Step 6: Subtract previously invoiced Threshold invoice total = $520.00 ───────────────────────────────────────── Remaining charges = $280.00 Step 7: Tax (10%) $280.00 × 0.10 = $28.00 ───────────────────────────────────────── Total with tax = $308.00 ``` ## Example 7: Complex scenario with all components A customer has: * **Usage price**: Tiered API calls (\$0.01 for first 10K, \$0.005 thereafter) * **Fixed fee**: \$100/month platform fee (in-arrears) * **Percent discount**: 15% (invoice-level) * **Minimum**: \$200 * **Prepaid credits**: \$150 USD * **Customer balance**: \$30 (from previous refund) * **Tax rate**: 8% **Usage**: 50,000 API calls **Step-by-step calculation**: ``` Step 2: Subtotals API calls (tiered): 10,000 × $0.01 = $100.00 40,000 × $0.005 = $200.00 API subtotal = $300.00 Platform fee (in-arrears) = $100.00 ───────────────────────────────────────── Total subtotal = $400.00 Step 3: Adjustments 3c. Percent discount (15%): $400.00 × 0.15 = $60.00 discount Distributed: API: ($300/$400) × $60 = $45.00 Fee: ($100/$400) × $60 = $15.00 After discount = $340.00 3d. Minimum ($200): $340.00 > $200, no change = $340.00 Step 4: Prepurchase Available: $150.00 Eligible (in-arrears only): API (in-arrears): $255.00 Fee (in-arrears): $85.00 Total eligible: $340.00 Credits applied = $150.00 ───────────────────────────────────────── After prepurchase = $190.00 Step 7: Tax (8%) $190.00 × 0.08 = $15.20 ───────────────────────────────────────── Total with tax = $205.20 Step 8: Customer balance Balance applied = $30.00 ───────────────────────────────────────── Amount due = $175.20 ``` ## Adjustment sign conventions When working with the API or debugging invoice calculations, understanding delta signs is important: | Adjustment Type | Delta Sign | Effect | | ---------------- | ---------- | ----------------------------------- | | Usage discount | Negative | Reduces quantity, lowering subtotal | | Amount discount | Negative | Directly reduces subtotal | | Percent discount | Negative | Reduces subtotal by percentage | | Minimum | Positive | Increases subtotal to meet floor | | Maximum | Negative | Reduces subtotal to meet ceiling | ## Debugging calculation discrepancies If an invoice total doesn't match expectations: 1. **Check adjustment order**: Adjustments apply in a specific sequence. A discount might reduce charges below a minimum, which then gets raised. 2. **Verify prepaid credit eligibility**: Only in-arrears charges are eligible for prepaid credit deduction. In-advance fixed fees are not. 3. **Confirm currency matching**: Prepaid credits must match the currency of the prices they're applied to. 4. **Review threshold billing**: If using threshold billing, check if amounts were already invoiced on a partial invoice. 5. **Inspect customer balance**: Customer balance is applied after tax, which can cause confusion if you're comparing pre-tax amounts. ## Advanced calculation scenarios This section covers how Orb handles specific edge cases to help you understand the expected behavior for complex billing configurations. ### In-advance charges and prepaid credits Prepaid credits apply only to in-arrears charges. If a price is configured to bill "in advance" (charged at the start of the period), it will not draw from prepaid credits. This applies regardless of whether it's a fixed fee or usage-based charge—the billing timing determines credit eligibility, not the fee type. This design ensures that prepaid credits reduce actual usage charges rather than upfront commitments, which aligns with how most businesses model usage-based prepayment. ``` Customer has: $1,000 prepaid credits Charges: In-advance platform fee: $200 (NOT eligible for credits) In-arrears usage charges: $300 (eligible for credits) ───────────────────────────────────────── Credits applied: $300 (to in-arrears only) Remaining credits: $700 Amount due: $200 (in-advance fee) ``` ### How invoice-level minimums are distributed When an invoice-level minimum is applied, the delta (the amount needed to reach the minimum) is distributed equally across all applicable line items. This equal distribution ensures consistent line-item attribution for reporting and revenue recognition. ``` Setup: Invoice-level minimum: $100 Compute charges: $30 Storage charges: $30 Total charges: $60 (below minimum) Calculation: Delta to reach minimum: $100 - $60 = $40 Per line item: $40 ÷ 2 = $20 each Result: Compute: $30 + $20 = $50 Storage: $30 + $20 = $50 Total: $100 (meets minimum) ``` ### Proration of minimums and maximums When a subscription starts or ends mid-cycle, minimums and maximums are prorated proportionally to the billing period served. A \$100/month minimum on a 15-day proration becomes a \$50 minimum. This ensures customers are only held to commitments proportional to their active period. ``` Setup: Monthly minimum: $100 Subscription starts mid-month (15 of 30 days) Calculation: Prorated minimum: $100 × (15/30) = $50 Actual charges: $30 Result: Minimum delta applied: $50 - $30 = $20 Final charge: $50 (prorated minimum) ``` ### Threshold billing and partial invoice reconciliation When threshold billing creates multiple partial invoices within a billing period, Orb tracks the highest amount invoiced per price. The end-of-period invoice subtracts this highest amount, ensuring accurate reconciliation when usage is recalculated between threshold events. ``` Scenario: Threshold: $500 Day 10: Usage hits $520, partial invoice issued Day 15: Usage recalculated to $650, new partial invoice issued Day 30: Final usage is $800 End-of-period calculation: Total charges: $800 Subtract highest partial: $650 (not $520 + $650) ───────────────────────────────────────── Remaining to invoice: $150 ``` # Invoice calculations Source: https://docs.withorb.com/invoicing/invoice-calculations In Orb, an invoice is a detailed statement that outlines the charges a customer owes for a specific billing period. Understanding how invoices are calculated will help you manage billing effectively and anticipate charges. Looking for worked examples? See [Invoice calculation examples](/invoicing/invoice-calculation-examples) for step-by-step walkthroughs of common scenarios including tiered pricing, adjustments, and prepaid credits. ## Basic definitions An invoice is composed of several key components: 1. **Line Items**: Each line item represents a charge associated with a specific `Price` object. 2. **Adjustments**: Modifications that can alter the invoice total or individual line items. Adjustments include: * **Minimums**: Ensures a minimum charge is applied. * **Maximums**: Caps the charge at a certain amount. * **Amount Discounts**: Subtracts a fixed amount from the subtotal. * **Usage Discounts**: Reduces the billable quantity. * **Percentage Discounts**: Reduces the subtotal by a percentage. Adjustments can be applied at the line item level or across multiple line items but must adhere to the following rules: * Applicable only to prices with a real-world currency. * Usage discounts apply only to quantities at the line item level. * Other adjustments can be applied at both the line item and multi-line item levels. * Adjustments must be applied to `Price`s with the same cadence (billing period), billing mode (in-arrears or in-advance), and currency. * Percentage discounts can span different cadences as they distribute proportionally. 3. **Taxes** (Optional): Calculated per line item if tax is enabled. 4. **Balance Transaction** (Optional): Derived from the customer's invoicing balance and applied after tax calculations. ### Important Invariants * **Unique Price Association**: Each line item is linked to a unique `Price` object, whether added manually or generated by the billing engine. * **No Duplicate Prices**: An invoice cannot contain multiple line items referencing the same `Price`. * **Currency Consistency**: All line items should be in a single real-world currency but can include multiple virtual currencies, each with a conversion rate to the real-world currency. * **Adjustment Association**: Each adjustment is tied to one or more `Price` objects. ## Calculating the true total for a line item The final amount due (`total_with_tax`) on an invoice is calculated by summing the "true total" of each line item. Each line item is calculated independently, and no calculations are exclusive to the invoice level, except for applying the customer invoice balance after taxes. ### Step-by-Step Calculation 1. **Determine Line Item Quantity** * The quantity is based on either: * The latest fixed fee quantity. * The total usage during the service period. * **Multiple Quantities**: If a `Price` covers multiple units (e.g., compute units per region), the line item will include a list of quantities, one for each unit type. 2. **Apply the Pricing Function** * Calculates the subtotal based on the quantity or quantities. * Outputs a monetary amount in the `Price`'s currency, which can be a real-world currency (e.g., USD) or a virtual currency (e.g., Database credits). * **Virtual Currencies**: Must have a specified conversion rate to the plan's real-world currency. * At this point, conversion rates are not yet applied if applicable. 3. **Apply Line Item Level Adjustments** Adjustments at the line item level are applied to the subtotal in the following order: * **Usage Discounts**: Reduces the billable quantity (e.g., `100 units off`). * **Amount Discounts**: Subtracts a fixed amount (e.g., `$100 off` or `100 credits off`). * **Percentage Discounts**: Reduces the subtotal by a percentage (e.g., `10% off`). * **Minimums**: Ensures the adjusted subtotal doesn't fall below a certain amount (e.g., `at least $10`). * **Maximums**: Caps the adjusted subtotal at a certain amount (e.g., `at most $100`). **Note**: Adjustments operate in the `Price`'s currency, which may be a virtual currency. 4. **Apply Multi-Price/Invoice Level Adjustments** * Adjustments that span multiple `Price`s (e.g., at the `Plan` level) are applied next. * The order of adjustments remains the same as above. * Must be applied to `Price`s sharing the same currency. If an adjustment is applied to an invoice through a subscription configuration (i.e. through an adjustment interval), the adjustment interval bounds determine whether the adjustment is applied to the invoice based on whether the invoice date falls into the adjustment interval. If the adjustment applies to in-arrears fees, then the adjustment interval is inclusive of its end bound. If the adjustment applies to in-advance fees, then the adjustment interval is inclusive of its start bound. In the case that it applies to both, the interval is \[inclusive, inclusive]. 5. **Apply Prepaid Credits** * Prepaid "credit balance" is applied to the adjusted subtotal. * Applicable to in-arrears charges only (not in-advance charges). * Credits must match the currency of the prices they are applied to. 6. **Apply Overage Conversion** * If using a virtual currency, apply the overage conversion rate to convert to a real-world currency. * Each price may have a different conversion rate, but all must convert to the same real-world currency. * After this step, the amount is in a real-world invoicable currency. 7. **Subtract Previously Invoiced Amounts** * For threshold-based invoicing, subtract any amounts previously invoiced during the billing period to avoid double billing. 8. **Apply Line Item Tax** * Taxes are calculated per line item using integrated tax providers like TaxJar or Avalara. * Taxes are added when the invoice is issued, not on draft invoices. * **External Invoicing Providers**: If syncing with providers like Stripe, Orb does not calculate tax; it must be added externally. ## Frequently asked questions ### How are adjustments that apply across multiple prices distributed? * **Proportional Distribution**: Adjustments (excluding minimums) are distributed based on each line item's proportion of the total subtotal. * **Example**: A \$12 discount applies to `Price A` and `Price B`. If `Price A` has a subtotal of \$5 and `Price B` has a subtotal of \$15: ``` // Calculating the discount distribution totalSubtotal = 5 + 15; // $20 priceADiscount = (5 / 20) * 12; // $3 priceBDiscount = (15 / 20) * 12; // $9 ``` * `Price A` receives a **\$3** discount. * `Price B` receives a **\$9** discount. * **Minimum Adjustments**: Distributed evenly among applicable line items. **Fixed-fee only invoices and minimums**: Minimum adjustments are not applied to invoices that contain only fixed fees. This rule prevents minimums from being applied to the first in-advance invoice for subscriptions that don't have any in-arrears usage-based fees, but applies more broadly to any invoice consisting entirely of fixed fees, regardless of timing or subscription type. * This distribution method does not affect the total invoice amount due. ### Why is my invoice below the set minimum amount? * **Prepaid Credits Impact**: Minimums are applied before prepaid credits. For instance, with a \$300 minimum and \$200 in prepaid credits: ``` // Calculating the final amount due adjustedSubtotal = max(subtotal, 300); // Apply minimum finalAmountDue = adjustedSubtotal - 200; // Subtract prepaid credits ``` * The **adjusted subtotal** is set to **\$300**. * **Prepaid credits** reduce the amount due to **\$100**. * **Proration**: Minimums may be prorated based on the service period. ### Can I always invoice a fixed amount regardless of the customer's prepaid balance? * **Not Possible**: Applying minimums after prepaid credits would overcharge the customer. ``` // Incorrect calculation that overcharges adjustedSubtotal = subtotal - 200; // Subtract prepaid credits first finalAmountDue = max(adjustedSubtotal, 200); // Apply minimum // This results in the customer paying $200 on top of the $200 prepaid credits used ``` * **Correct Approach**: Orb applies minimums before credits to prevent overcharging. ### Can I have an adjustment that applies only to overage charges? * **No**: Adjustments are always applied before prepaid credits, affecting the total amount before considering overage. ### What's the difference between invoice balance and prepaid credits? * **Invoice Balance**: * Acts like a "payment method" applied after taxes. * Used to apply a future amount to the next invoice. * **Prepaid Credits**: * Applied before taxes. * Function as a usage commitment. * Only applicable to in-arrears charges (including both usage-based prices and fixed fees billed in arrears). * Typically have start and expiration dates. ### When might Orb produce a negative invoice amount? * **Negative Invoices**: Can occur during certain fixed fee downgrades. * **Credit Notes**: Usually, credits from downgrades are applied as credit notes on the original invoice instead of creating a negative invoice. ### How are prepaid credits in a currency distributed across prices in the same currency? * **Stable Distribution**: Credits are allocated consistently based on price IDs. * **Calculation Impact**: The specific distribution does not affect the overall invoice total. # CSV downloads Source: https://docs.withorb.com/invoicing/invoice-csv-downloads You can export detailed invoice information as a CSV file, directly from the Orb UI.  Orb supports two CSV file types: 1. **Invoice CSV export**: invoice and credit note details at the record level 2. **Invoice line item CSV export**: invoice and credit note details at the individual line item level Exports are ideal for: 1. Reconciling transactions with other systems 2. Performing custom financial analyses 3. Sharing records with external teams or auditors How it works: 1. Navigate to the [Invoices](https://app.withorb.com/invoices) page in Orb. 2. Apply any filters (e.g., invoice date range, customer, invoice status), or select individual invoices to narrow down your dataset. 3. Click “Export” for the Invoice CSV. Click “Export line item detail” in the overflow menu for the Invoice line item CSV.  Invoicecsvdownloads Pn ## Invoice CSV | **Column Name** | **Description** | | :------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | Invoice ID | The ID of the invoice. | | Invoice date | The invoice document date set on creation. | | Net invoice amount | The total amount due on the invoice, inclusive of all [invoice calculations](https://docs.withorb.com/invoicing/invoice-calculations), excluding tax. | | Net amount | The total amount on the invoice, inclusive of all [invoice calculations](https://docs.withorb.com/invoicing/invoice-calculations) and credit notes, excluding tax. | | Total tax amount | The total tax amount applied to the invoice. | | Currency | The real-world invoicing currency for this invoice. | | Invoice number | The identifying invoice number. | | External customer ID | The external ID of the customer. | | Customer ID | The Orb ID of the customer. | | Customer name | The name of the customer. | | Customer tax code | The tax code applied to the customer. (optional) | | Customer tax ID | The tax ID associated to the customer. (optional) | | Due date | The time the invoice must be paid by. (MM/DD/YYY) | | Invoice status | The current [status](https://docs.withorb.com/invoicing/structure#invoice-states) indicator for the invoice. | | Subscription ID | The ID of the subscription. This value is null for one-off invoices. | | Paid at | The time that the invoice was paid. | | Payment failed at | The time the invoice payment failed. | | Plan ID | The ID of the plan. This value is null for one-off invoices. | | Plan name | The name of the plan. | | Invoice type | The [type of invoice](https://docs.withorb.com/invoicing/structure#types-of-invoices) (one-off, recurring, partial). | | Customer email | The email of the customer. | | Credit note date | The credit note document date set on creation. (MM/DD/YYY) | | Credit note ID | The ID of the credit note. | | Credit note number | The unique identifier for credit notes. | | Credit note amount | The total amount to be credited. | | Credit note type | The type of [credit note](https://docs.withorb.com/invoicing/credit-notes) (refund, adjustment). | | Credit note reason | The reason for the credit note. (optional) (Duplicate, Fraudulent, Order change, Product unsatisfactory.) | | Credit note status | The current status indicator for the credit note. | ## Invoice line item CSV | **Column Name** | **Description** | | :-------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Invoice ID | The ID of the invoice. | | External customer ID | The external ID of the customer. | | Customer ID | The Orb ID of the customer. | | Customer name | The name of the customer. | | Invoice number | The identifying invoice number. | | Invoice date | The invoice document date set on creation. (MM/DD/YYY) | | Due date | The time the invoice must be paid by. (MM/DD/YYY) | | Memo | The optional memo for the invoice. | | Line item name | The name of the invoice line item. | | Invoice line item start | The start date of the associated line item. (MM/DD/YYY) | | Invoice line item end | The inclusive end date of the associated line item. (MM/DD/YYY) | | Quantity | The quantity of the associated line item. | | Gross amount | The subtotal, or list price, of the associated line item, before any adjustments. | | Gross amount currency | The pricing currency of the associated line item. This can be a custom pricing unit. | | Prepaid credits applied amount | The total amount of credits applied to the associated line item. | | Prepaid credits applied amount currency | The pricing currency of the credits applied. This can be a custom pricing unit. | | Net invoice line item amount | The total amount of the associated line item, inclusive of all [invoice calculations](https://docs.withorb.com/invoicing/invoice-calculations), excluding tax. | | Net amount | The total amount of the associated line item, inclusive of all [invoice calculations](https://docs.withorb.com/invoicing/invoice-calculations) and credit notes, excluding tax. | | Tax amount | The total tax amount applied to the line item. | | Invoice currency | The real-world invoicing currency for this invoice. | | Invoice status | The current [status](https://docs.withorb.com/invoicing/structure#invoice-states) indicator for the invoice. | | Subscription ID | The ID of the subscription. This value is null for one-off invoices. | | Paid at | The time that the invoice was paid. | | Plan ID | The ID of the plan. This value is null for one-off invoices. | | Plan name | The name of the plan. | | Invoice type | The [type of invoice](https://docs.withorb.com/invoicing/structure#types-of-invoices) (one-off, recurring, partial). | | Customer email | The email of the customer. | | Credit note date | The credit note document date set on creation. (MM/DD/YYY) | | Credit note ID | The ID of the credit note. | | Credit note number | The unique identifier for credit notes. | | Credit note amount | The total amount to be credited for the associated line item. | | Credit note amount currency | The pricing currency of the associated line item to be credited. This can be a custom pricing unit. | | Credit note type | The type of [credit note](https://docs.withorb.com/invoicing/credit-notes) (refund, adjustment). | | Credit note reason | The reason for the credit note. (optional) (Duplicate, Fraudulent, Order change, Product unsatisfactory.) | | Credit note status | The current status indicator for the credit note. | | Credit note line item start | The start date of the associated line item to be credited. (MM/DD/YYY) | | Credit note line item end | The inclusive end date of the associated line item to be credited. (MM/DD/YYY) | # Invoice delivery Source: https://docs.withorb.com/invoicing/invoice-delivery Orb can directly deliver invoices to your customers by email without any additional integration work or the use of a dedicated email provider. Each email contains a preview of the invoice in its body, attaches the invoice PDF, and links to the Orb invoice portal to submit payment. ### Configuring delivery If your account is set to deliver emails automatically, emails will be sent from an Orb email address (`invoices+@withorb.com`) to the primary email address configured for the customer, with additional email addresses included as CC recipients. The `reply-to` email address can be configured to an email address of your choosing. Invoice delivery settings can be found on the [Invoice Settings](https://app.withorb.com/settings) page in Orb. The "Automatically send Orb invoice emails" radio button is used to control email delivery across your account. Items under "Configurations" show the email(s) available to send Orb invoices from. Invoice email delivery settings Manually triggering email delivery can be done from the invoice page. Note that Orb does not automatically send emails for invoices in test mode, and also does not send emails for \$0 invoices which are marked as paid automatically. Invoice email delivery ### Custom email domain setup Custom email domains are not included with all Orb billing plans by default. Please reach out to your Orb representative if you have any questions. Orb supports sending invoice emails from your own domain, giving you the ability to have more control and consistency in the invoicing experience. Configuration of custom email domains requires reaching out to Orb support. Once configured, custom email domains will show up under [Invoice Settings](https://app.withorb.com/settings) similarly to Orb's default email. Invoice email delivery settings custom ### Configuring company logos on invoices You can add and update your company logo that appears on your invoices by going into the [settings](https://app.withorb.com/settings), under "Invoice formatting". This logo will appear on your web invoices, invoice pdfs, and emails. When you configure your logo in Orb settings, it automatically propagates to the customer portal, ensuring consistent branding across all customer-facing interfaces. For best results, your logo should * have a transparent background * use darker version of the logo (your logo will always be against a white background) * be limited in height to 40px and should not exceed 1200px in width Logo settings ### System email types Orb automatically delivers the following emails: * **Invoice issued**: delivered as soon as the invoice transitions to the issued state. Note that if the invoice is immediately auto-collected (i.e. it's due on issue), this email will not be sent in lieu of a payment success or failure email. This email type can also be triggered manually via the Orb web application. * **Payment success**: delivered when an invoice transitions to paid, whether as a result of auto-collection or manual payment through the invoice portal. This email includes a payment receipt for the transaction. * **Payment failure**: delivered when Orb's auto-collection functionality fails (for example, due to insufficient funds or a different card error). The customer may retry the payment with another payment method through the invoice portal, or payment for the invoice will be retried via Orb's dunning behavior. ### Invoice receipts Orb automatically generates a payment receipt when an invoice is successfully paid. Receipts are created at the moment the invoice transitions to the paid state, whether payment is collected via auto-collection or manually through the invoice/customer portal. Customers should check their payment provider settings to ensure that payment receipts are configured as needed (either enabled or disabled according to their preference). # Invoice display Source: https://docs.withorb.com/invoicing/invoice-display Navigate to the [Invoices settings](https://app.withorb.com/settings) page to configure the display of your invoices. ### Hide zero quantity line items This setting will hide any usage line items or fixed fees with a quantity of 0 from the invoice. If a minimum has been applied to the invoice line item then the line item will be displayed even if the quantity is zero. The setting only applies to the top level usage for any line item. For example, if you're using a tiered pricing plan, the line item will be hidden only if there is no usage from any tiers. Hide zero quantity line items ### Hide voided invoices This setting will hide any invoices that have been voided from the customer portal. All invoices will still be visible in the Orb product. Hide voided invoices # Invoice issuance Source: https://docs.withorb.com/invoicing/invoice-issuance Orb's invoice issuance system controls when and how invoices transition from draft to issued status. This guide covers configuration options and issuance behavior for different invoice types. ## How invoice issuance works Invoice issuance is the process of finalizing a draft invoice and making it available to customers. Once the invoice is issued, its values cannot be changed. When an invoice is created in Orb, it starts in draft status. Depending on your account settings and customer settings, invoices will either: * **Auto-issue**: Automatically transition to issued status after a grace period * **Require manual approval**: Remain in draft until manually issued via the dashboard or API Invoice issuance settings do not apply to one-off invoices created manually The issuance process handles freezing values on the invoices. Only if configured, the following actions may also occur on the invoice: * tax calculations through a configured tax provider * payment method auto-collection through Stripe * email delivery to the customer ## Draft vs. Action Needed statuses **Draft** * The initial state when an invoice is created * Indicates the grace period has not passed, and is not eligible to be issued by Orb or the user **Action Needed** * A draft invoice that is eligible for manual issuance * Shown in the dashboard when manual approval is required * Indicates the grace period has passed, and the invoice can be safely issued **Pending issue** * An invoice that has been submitted for issuance and is being finalized asynchronously * Can appear after manually issuing usage-based invoices or when Orb needs to avoid concurrent invoice updates * Requires no additional action; Orb will complete issuance in the background ## Grace period and issuance timing Orb applies different issuance timing based on invoice content to ensure accuracy and provide flexibility for corrections. ### Usage-Based Invoices Invoices containing usage-based line items respect a grace period before becoming eligible for issuance: * **Default grace period**: 12 hours after the invoice period ends * **Purpose**: Allows time for late-arriving usage events to be processed ``` Invoice Period End → Grace Period (12h) → Buffer → Eligible for Issuance ``` ### Fixed-Price Invoices Invoices containing only fixed-price line items (subscriptions, one-time charges) have no grace period: * **Immediate eligibility**: Available for issuance right after creation ### Partial Invoices Invoices generated outside the normal billing cycle (e.g. threshold invoices): * **No grace period**: Bypass the standard grace period ### Increment Credit Invoices * Generated for single credit purchases or payment plans * Auto-issues according to account or customer settings ## Account-level settings Account-level settings control the default issuance behavior for all customers and invoices. Invoice issuance method setting ### Manual vs Automatic Issuance **Automatic Issuance** * Invoices auto-issue after grace periods and buffers * Requires no manual intervention * Processed by Orb asynchronously **Manual Issuance** * All invoices require manual approval before issuance * Invoices appear as "Action Needed" when eligible * Provides control and review opportunity * Recommended for enterprise billing scenarios When changing from manual issuance to automatic issuance, you will have the option to apply the setting to all historical invoices. This will automatically issue any invoices that would have been eligible to issue. If unchecked, the setting will only apply to invoices with an invoice date after the time of the setting change. Apply automatic issuance to past invoices option ## Customer-level settings Customer-level settings enable you to override account defaults for specific customers, allowing for mixed billing workflows within a single account. ### Override Behavior **Customer Override Options** * **Inherit from account (Default)**: Use account-level setting * **Automatic issuance**: Always auto-issue for this customer * **Manual issuance**: Always manual approval for this customer If the customer-level setting is specified to automatic or manual issuance, the customer's setting will not change, even if the account-level setting is changed. ### Inheritance Hierarchy The system applies settings in order of specificity: 1. Account-level defaults 2. Customer-level overrides ### Use Cases **Enterprise + Self-Serve Mix** * Account default: Manual issuance * Enterprise customers: Manual issuance (inherit) * Self-serve customers: Automatic issuance (override) **High-Touch Accounts** * Account default: Automatic issuance * VIP customers: Manual issuance (override for review) * Standard customers: Automatic issuance (inherit) ### API Configuration Update customer issuance settings via the customer update endpoint: ```json theme={null} { "auto_issuance": true // true = auto, false = manual, null = inherit } ``` ## Manual issuance process When manual issuance is enabled, invoices require explicit action to transition from draft to issued. ### Dashboard Workflow 1. Navigate to the Invoices section 2. Filter for Action Needed invoices 3. Review invoice details and line items 4. Click Issue Invoice to finalize. Some invoices may briefly move to Pending issue while Orb completes finalization in the background. ### API Issuance Issue invoices programmatically via the REST API: ```bash theme={null} curl -X POST "https://api.withorb.com/v1/invoices/{invoice_id}/issue" \ -H "Authorization: Bearer $ORB_API_KEY" ``` **Requirements:** * Invoice must be in draft status * Grace period must have elapsed (for usage-based invoices) * Cannot be used for invoices set to auto-issue ## Best practices ### Choosing Issuance Mode **Use Manual Issuance When:** * Complex usage calculations require review * Custom pricing or contract terms need verification * Regulatory compliance requires approval workflows * Invoice volumes are manageable for manual review **Use Automatic Issuance When:** * Standardized pricing with minimal exceptions * High invoice volumes make manual review impractical * Usage calculations are well-tested and reliable * Faster invoice delivery is prioritized ### Grace Period Configuration **Standard Setup: 12-hour grace period with minimal buffers** * Balances accuracy with timely delivery * Suitable for most usage-based billing scenarios **Conservative Setup: 24-hour grace period with 2-hour buffers** * Maximum accuracy for complex usage scenarios * Slower but more thorough processing **Aggressive Setup: 6-hour grace period with no buffers** * Faster invoice delivery * Requires confident usage event processing To change your account's grace period, please contact Orb support for assistance. We may not be able to honor every request, depending your stage of integration and account setup. ### Mixed Customer Configuration For accounts with diverse customer needs: 1. Set account default to most common preference 2. Override specific customers as needed 3. Monitor issuance patterns and adjust accordingly 4. Use customer segments to batch similar configurations # Invoice portal Source: https://docs.withorb.com/invoicing/invoice-portal ## Share the invoice and customer portal Orb generates a signed, expiring URL to an *invoice portal* and a signed, non-expiring URL to a *customer portal*. By default, the invoice portal link expires 30 days after the invoice's due date. These portals can be directly accessed by your customers and are symmetrical to the internal version of the Orb invoice and a full view of a customer's invoices and current usage respectively, reflecting Orb’s philosophy around transparency in the customer relationship. These portals can serve as an out-of-the-box tool for displaying a complete view of the upcoming bill for your customer and the history of all invoices alongside their payment status. There are multiple ways to integrate this information in your app: 1. You can directly embed the invoice portal for an individual invoice using an iFrame in your application. 2. You can extract details from the invoice resource programmatically and build a custom experience on top of the invoice data. Orb also exposes the cost and usage APIs for the day by day graphs for each metric in the plan. 3. The invoice resource, and customer resources with permanent portal links enabled, expose the signed resource link directly, allowing you to link to the Orb-hosted portals from your app. Invoice portal ### Customer portal sessions For customer portal access, Orb also supports short-lived portal session URLs. Use [`POST /v1/customers/{customer_id}/portal_sessions`](/api-reference/customer/create-portal-session) to create a session URL that expires after `expires_in_minutes` (60 minutes by default, with a maximum of 180 minutes). By default, creating a new portal session invalidates any other active portal sessions for that customer. If you need multiple concurrent session URLs, for example when minting links for several authenticated end users, set `invalidate_existing` to `false`. Creating a portal session does not invalidate the customer's permanent portal link. Accounts can enable a Customer portal sessions only setting in Invoice Settings. When enabled, new customers do not receive permanent customer portal URLs, and the `portal_url` field on the customer resource returns `null`. Existing permanent links continue to work. This setting is one-way and cannot be disabled after it is enabled. ### Portal payments The invoice and customer portal embed a form from the payment gateway that allows your user to pay invoices manually. This payment option can be configured to optionally save the payment method to the customer, and can be restricted to specific payment types (e.g. configuring invoices to only be payable via ACH). Customer portal ### Expiring links By default, the signed link for the invoice portal expires 30 days after the invoice due date and is delivered to your customers in the invoice email (see [Email delivery](/invoicing/invoice-delivery)). This link can be refreshed at any time through the portal or revoked for any reason, which will be valid for 60 days. A single link can be active for an invoice at a time. Revoke link # One-off invoices Source: https://docs.withorb.com/invoicing/one-off-invoice-creation For one-time or ad-hoc charges your customers may incur, you can create one-off invoices. One-off invoices inherit all the capabilities of invoices created from subscriptions and can be created via the dashboard or the API. In the dashboard, navigate to the invoices page and click "+ New Invoice" at the top right. You can also enter this flow from the invoice section of a specific customer page. Fill in any invoice details and line items. As you add and edit line items, the invoice subtotals and totals will automatically update. one-off-invoice Upon clicking "Save", you will be guided to a draft version of the invoice where you can review the totals before you decide to issue. Once manually issued, any configurations regarding auto-collection and email delivery will be inherited from your account settings. # Payments Source: https://docs.withorb.com/invoicing/payments Orb integrates with payment gateways like Adyen and Stripe for payment processing. Once a customer in Orb is mapped to the corresponding payment gateway, Orb can automatically pull in the relevant payment information. ### Payment attempts (formerly auto-collection) If payment attempts are enabled for a customer or subscription, Orb will attempt to charge the default payment method on the invoice due date and any configured retries from Essential Dunning or Automations. Payment Attempts Auto Collect To locally exclude specific customers or subscriptions from payment attempts, do so on the individual customer or subscription record. Exclude Payments ### Configuring net terms and collection behavior The due date of an invoice is determined by its "net terms", which represents the days offset from the invoice date or issuance date, depending on the calculation method selected. This setting can be found and updated in the [Invoices](https://app.withorb.com/settings?tab=invoices) tab of your account settings. Invoice settings net terms configuration Choose "net terms" from the available templated date ranges in the dropdown, or provide a custom due date. Net terms This is configurable at the plan-level, but can be overridden per subscription or invoice. It’s common for enterprise customers to have extended terms (e.g. 30 or 60 days) and self-serve customers to have invoices that are due on issue (which reflects a net-terms value of 0). Unlike net-terms behavior, auto collection can be configured on a per-customer basis. If a customer is initialized with a linked payment provider customer, auto-collection is turned on by default, but can be manually disabled. Similarly, if a customer is later linked to a payment provider customer, auto-collection is automatically enabled. As with net-terms, auto-collection can be turned off for individual invoices. Payment terms ### Payment methods For gateways that expose a default payment method on the customer record (such as Stripe), Orb automatically syncs that default and displays it on the customer page. For gateways without a synced default (such as Adyen), you designate the customer's default payment method yourself. See [Managing payment methods via the API](/integrations-and-exports/adyen#managing-payment-methods-via-the-api). In either case, the payment method used for a given invoice is shown on the invoice itself and locked once the invoice is paid to reflect the actual transaction. Orb supports all payment methods available through the connected payment gateway for one-time payments. However, when saving a payment method to use as the customer’s default, only a subset of those methods may be eligible—depending on the gateway. For example, with Stripe, one-time payments can be made using credit cards, ACH transfers, Stripe Link, digital wallets (Apple Pay, Google Pay), and crypto. Saved payment methods, however, are typically limited to credit cards, ACH, and Stripe Link. For automatic payments (e.g. autopay when an invoice is issued), Orb supports credit cards, ACH transfers, Stripe Link, Apple Pay, Google Pay, and Amazon Pay. If you would like to support any other payment methods for recurring payments, please contact the Orb team. Customer details acme ### Payment method selection When charging a customer through **Stripe**, Orb determines which payment method to use by checking the following fields in order of priority: 1. Stripe's `customer.invoice_settings.default_payment_method` - The explicitly configured default payment method 2. Stripe's legacy `customer.default_source` field - Used for backward compatibility with older Stripe integrations 3. Most recently created payment method - If neither of the above are set, Orb automatically uses whichever payment method was most recently attached to the customer in Stripe #### What this means for your customers * **Customers without an explicit default**: Even if a customer doesn't have a default payment method explicitly configured in Stripe, Orb will still attempt to charge them using whichever payment method was added most recently * **Automatic fallback**: This happens automatically without any configuration needed on your end * **No payment method scenario**: The only scenario where a charge would fail due to missing payment information is if the customer has no payment methods attached at all in Stripe #### Best practices While the automatic fallback to the most recent payment method provides convenience, we recommend ensuring customers have an explicit default payment method set in Stripe (`invoice_settings.default_payment_method`) for clarity and predictability. Relying on explicit defaults gives you more control over which payment method gets charged and avoids potential confusion if customers have multiple payment methods on file. ### Excluding payment methods When using Orb Invoicing with Stripe as a payment gateway, you can exclude specific Stripe payment method types on a per-customer basis. This is useful for enterprise contracts or sales-led deals where credit card processing fees may be prohibitively expensive. Excluded payment methods will not be available for the customer to select during payment and will not be used for auto-collection. If a customer's default payment method becomes excluded, Orb will attempt to use the next available compatible payment method for auto-collection. Excluded payment methods can be configured per-customer in the payment configuration section of the customer's details page, or via the API using the `payment_configuration` field. Excluded payment methods For the full list of payment method types, see Stripe's [PaymentMethod documentation](https://docs.stripe.com/api/payment_methods/object#payment_method_object-type). ### Distinguishing invoice types in payment workflows When handling payment webhooks or building payment recovery flows, use the `invoice_source` field to differentiate between invoice types: * `invoice_source=subscription`: Recurring charges from the customer's subscription * `invoice_source=one_off`: One-time charges such as credit purchases or manual invoices This distinction is useful for: * **Payment failure handling**: Retry subscription invoices more aggressively than one-off credit purchases. Subscription failures may indicate a need to block product access, while credit purchase failures mean the credits aren't granted. * **Customer communication**: Send different messages for recurring billing failures versus failed credit purchases. A subscription payment failure might trigger account suspension warnings, while a credit purchase failure is less critical. * **Analytics and reporting**: Separate subscription revenue from prepaid credit revenue to understand different revenue streams. See the [invoice schema](/api-reference/invoice/fetch-invoice) for all available fields. ### Payment receipts PDFs Orb generates receipts when an invoice is successfully paid. Receipt generation happens at the moment the invoice transitions to the paid state, regardless of whether payment was collected via auto-collection, through the invoice/customer portal, or is marked as paid. How to access receipts: * A receipt is included as an attachment in the payment success email that Orb sends to the customer when the invoice is paid via auto-collection. * In the Orb dashboard, open any paid invoice to download the receipt and invoice PDFs. * In the customer-facing invoice portal, the invoice reflects a Paid status and remains available for download. ### Automatic balance carryover for small invoices For customers using Stripe Payments, Orb can automatically handle invoices that fall below Stripe's minimum charge threshold. This feature helps avoid payment failures and customer confusion for small invoice amounts. #### How it works When enabled, Orb will: 1. **Detect small invoices**: During invoice finalization, identify invoices below the minimum threshold 2. **Apply balance automatically**: Deduct the invoice amount from the customer's account balance 3. **Mark as paid**: The invoice is marked as paid (no payment processing occurs) 4. **Carry forward balance**: The unpaid amount becomes a negative customer balance that carries forward to future invoices #### Configuration This feature can be configured in your Stripe Payments settings: * Navigate to **Settings > Payments > Stripe** * Toggle "Rollover small invoices" * The feature is enabled by default when setting up Stripe * Applies to all Orb-handled invoices (not externally synced invoices) #### Minimum thresholds The threshold varies by currency and matches [Stripe's minimum charge amounts](https://docs.stripe.com/currencies#minimum-and-maximum-charge-amounts). Orb uses these same minimums to determine when an invoice should be automatically rolled over. The minimum threshold is determined by the invoice currency and cannot be customized. #### Customer experience From the customer's perspective: * Small invoices are automatically marked as paid without requiring action * No "Payment Failed" emails are sent for amounts below the threshold * The carried balance appears as a line item on the next invoice * Once the accumulated balance exceeds the minimum threshold, normal payment processing resumes ## Tax configuration Refer to the [tax automation integrations](/integrations-and-exports/tax) documentation to learn how to use an automated tax provider to enrich your invoices with tax information. Note: Some existing Orb accounts may still be on Orb's legacy overrides behavior, which dynamically creates an entirely new plan to represent overrides rather than replacing the minimal set of prices and adjustments on the existing plan. If you're unsure which behavior your Orb account is using for overrides, please reach out to support. Because Orb does not serve as your invoicing provider when an invoice is synced, actions that cause the invoice to be modified in Orb will not reflect in the third-party solution. For example, a backdated action that causes the invoice to be voided will not reflect in the invoicing provider. Be sure to test this workflow when using backdated actions in combination with third-party invoicing solutions. # Structure and lifecycle Source: https://docs.withorb.com/invoicing/structure Invoices are a business document which are associated with a customer and optionally an Orb subscription. Each invoice contains: * Metadata about you as a vendor (e.g. your logo, name, business address) * Metadata about your customer (e.g. shipping and billing address, tax information) * A list of line items representing accrued charges, usually for an active billing period. These line items ladder up to an amount due for the end user. * Invoice terms, including its due date and auto-collection settings Unlike other billing providers, Orb’s invoices have been reimagined from the ground up to support the sophisticated needs of hybrid and usage-based models. An Orb invoice displays the intricacies of models like tiered pricing and allow you to structure by dimensions determined from usage events. Orb invoices also display a day-by-day summary of usage inline, allowing your customers to understand trends and spikes in each of the usage metrics on the invoice. Each Orb invoice also has a footer (configurable in account settings) as well as a memo, which can be customized per plan and changed on any individual invoice. ### Types of invoices Invoices can either be associated with a recurring subscription or represent an ad hoc charge. For example, when adding pre-purchase credits to an Orb customer, Orb can issue a one-off invoice based on the cost-basis of these credits — this invoice is not associated with a subscription, and does not inherit any prices from a plan. ### Custom invoice grouping It can be useful to group invoice line items in a more meaningful way for your customers than the individual prices that are configured in Orb. For example, as an infrastructure provider you might have a plan with prices corresponding to charges for storage, network traffic, and compute across each cluster that your customers operate. By default, this would generate an invoice that breaks down each of these costs aggregated across all clusters. If each of your reported events to Orb contains a `cluster_name`, then Orb can automatically re-organize your invoice to be grouped by the cluster’s name at the top level. Orb would then automatically split the costs of storage, network traffic, and compute for each cluster. Custom invoice grouping is only available for non-decomposable metrics (metrics that are possible to incrementally compute, e.g., `SUM` or `COUNT`). ## Invoice states Orb manages your invoice lifecycle automatically while still allowing you to use manual actions (like manually voiding, issuing, and marking invoices as paid) when you need them to maintain control. For the vast majority of workflows, Orb Invoicing's integration with the underlying billing system and payment gateway eliminates manual process entirely. Invoice status colored Every active subscription in Orb has an associated **draft** invoice, representing the accrual of charges in the current billing period; invoices associated with subscriptions never need to be manually created or refreshed, and can be set up to issue manually or automatically. While in draft state, the values on an invoice are subject to change, and often will do so continuously as usage is happening. Since Orb automatically maintains the values on this draft invoice throughout the billing period, the preview of a customer’s bill is always available in the invoice format. Once a subscription billing period ends — most commonly at the end of a month — the associated invoice enters a configurable grace period, during which values may still change as a result of late event reporting. Orb will not accept events with timestamps before the current grace period, ensuring that an invoice always reflects the entirety of events that have been ingested. After the grace period is over, the invoice is eligible to be issued. There are two ways an invoice can transition to the issued state: * If your account is configured to issue invoices automatically, the invoice will immediately be issued. * If your account is configured to require manual issuance of invoices, the invoice will transition to the action needed state. It can then be manually issued from the webapp or API. Once an invoice is `issued`, it can: * Transition to the `paid` state. This may happen as the result of an Orb-initiated auto-collection action, a user paying the invoice through the invoice portal, or the invoice being manually marked as paid. Note that \$0 invoices are automatically marked as `paid` upon issuance and do not send email notifications. * Transition to the `synced` state. Orb supports syncing to enterprise invoicing providers, such as Bill.com and Stripe Invoicing — this is a one-way sync, and Orb will not pick up changes to the invoice once it’s synced. * Transition to the `void` state by being manually marked as void. When an invoice is voided, any balance it consumes is reversed. Note that the invoices list in the dashboard will display additional statuses, including the `Payment failed` state (representing either invoice that failed to issue due to a misconfiguration, or a failed payment), a `Processing` state (representing invoices whose payment is in progress), and an `Action needed` state (representing invoices that require manual issuance). The dashboard is optimized for actionability, allowing you to quickly triage invoices that require your attention and to provide an easy way to summarize the month’s invoices. Once an invoice's grace period has elapsed and it is eligible for auto-issue, it will be in a `pending issue` state in the Orb dashboard, to signify that Orb will shortly issue the invoice. No manual action is required in this state. ### Test mode vs live mode invoice deletion Orb allows different invoice deletion behaviors depending on whether you are operating in test mode or live mode: **Test mode:** * Invoices can be deleted even after they have been issued * This provides flexibility for testing and development scenarios where you may need to clean up test data * Deletion is supported for invoices in any state (draft, issued, paid, etc.) **Live mode:** * Issued invoices cannot be deleted to maintain data integrity and compliance * This restriction helps ensure accurate financial records and audit trails * For issued invoices that need to be corrected, use [credit notes](/invoicing/credit-notes) or [void the invoice](/invoicing/structure#invoice-states) instead The test mode environment is completely isolated from live mode, so any invoice deletion in test mode will not affect your production data or financial records. ### Synced invoices If you're migrating to Orb with an existing invoicing solution in place, Orb's invoicing model allows you to maintain these connections via [Bill.com](https://bill.com/), [Stripe Invoicing](https://stripe.com/invoicing), [QuickBooks Invoicing](https://quickbooks.intuit.com/global/invoicing/), and [NetSuite](https://www.netsuite.com/). With these invoicing connections in place, Orb does not handle email delivery of your invoice, and cannot track the invoice status. If the invoice is paid (or manually edited or voided), these changes do not automatically reflect on Orb's invoice. **Setup** To set up these invoicing methods, two mappings need to be configured: * A mapping between Prices and Items in Orb to the corresponding Services or Products in the invoicing provider. This ensures that line items will be labeled appropriately. * The link between an Orb customer and a customer in the invoicing provider, in order to properly attribute the invoice. Note that this also requires setting the `payment_provider` attribute on the customer to the corresponding provider. Once in place, the sync process takes place automatically for invoices eligible to be issued when they are connected to an externally invoiced customer. **Scope** This invoice sync is a comprehensive integration, and includes native support for: * Individual line items, including amount, quantity, and rate information. * Invoice due date and terms (e.g. Net 30 days) * Line items for discounts and minimums, both for individual line-items and for the plan as a whole * Tax, as configured in the invoicing provider — Orb does not send tax amounts on synced invoices Because Orb does not serve as your invoicing provider when an invoice is synced, actions that cause the invoice to be modified in Orb will not reflect in the third-party solution. For example, a backdated action that causes the invoice to be voided will not reflect in the invoicing provider. Be sure to test this workflow when using backdated actions in combination with third-party invoicing solutions. For the purpose of credit note generation, a synced invoice is treated as always having been "paid", so all credit notes issued against this invoice will be treated as "refund" credit notes and add to the customer balance as necessary. Invoices with the "Synced" status can still be voided in Orb, but this action will not be reflected in the external invoicing provider. **Invoice numbering** For those using Quickbooks, invoice numbering respects the [custom transaction numbers](https://quickbooks.intuit.com/learn-support/en-us/help-article/customize-forms/change-invoice-number-sales-forms/L9KLFfphX_US_en_US) setting configured. * If custom transaction numbers are disabled, * QuickBooks will use its own numbering sequence (next available number) * The Orb invoice number gets stored in a separate custom field called "Orb Invoice Number" for reference * If custom transaction numbers are enabled, * Orb will push the Orb invoice number as the Quickbooks invoice number (`DocNumber` field) * Note: as long as the Orb invoice number is under 22 characters given Quickbook's limitation of 21 characters. To set up these syncs, click [here](/integrations-and-exports/introduction) for instructions. # Threshold Invoicing Source: https://docs.withorb.com/invoicing/threshold-invoicing Subscriptions in Orb can be set up with an invoicing threshold, which is an amount in the invoicing currency (e.g. USD). Specifically, thresholds are always expected to be a positive amount and apply at the full scope of the subscription rather than for individual prices. Note that threshold evaluation only considers usage-based line items. Fixed fees are excluded from threshold calculations to avoid triggering immediate invoicing at the start of the billing period. Once the threshold for a subscription is reached, a threshold invoice will be generated which will contain all the prices on the subscription. Threshold invoices bypass the [reporting grace period](/events-and-metrics/reporting-errors#reporting-grace-period) and will be issued immediately if eligible for issuance. As a consequence, Orb does not guarantee that all usage corresponding to the line item period on the threshold invoice will be included; any usage that has not yet been ingested will still be billed in the "parent" full invoice (or in a subsequent threshold invoice). If the threshold is not reached by the end of the billing period, a standard invoice will be issued as usual. The amounts from all threshold invoices will be applied to the appropriate usage line items when the full billing period invoice is issued. ## Monitoring the invoicing threshold The invoicing threshold is monitored in the background. If an account’s usage significantly exceeds the threshold, an invoice may be generated that surpasses the threshold amount. To update the invoicing threshold set the property `invoicing_threshold` when using the [Update Subscription API](/api-reference/subscription/update-subscription#update-subscription). Alerts on subscriptions can be setup to alert when particular thresholds have been reached. See the [Alerts API](/api-reference/alert/list-alerts) for information on how to setup alerts. ## Mechanics of threshold invoicing As a subscription accrues usage, the invoicing threshold is evaluated constantly and a threshold invoice is generated as soon as Orb detects that the threshold has been reached. Threshold invoices may exceed the exact threshold amount (especially if usage is accruing quickly or reported in large chunks), but will be issued as soon as the threshold is reached. If usage exceeds a multiple of the threshold before a threshold invoice is issued, Orb will issue a single invoice for the full incremental amount. Note that the invoicing threshold is evaluated on a per-subscription basis. If a customer has multiple subscriptions, each subscription will be evaluated separately. For a given billing period, each threshold invoice represents a *prefix* of the total invoice for that period. Specifically, this means that subsequent threshold invoices are always a strict superset of previous threshold invoices, and Orb ensures that the previously invoiced amount is not double-counted by subtracting this out during invoice line item calculations. This means that threshold invoice periods are not disjoint periods, which would otherwise cause incorrect billing behavior in pricing models that have a tiering or allocation component that cannot reset during a single billing period. ## Additional considerations ### Do threshold invoices include adjustments like minimums, maximums, and discounts? Yes. Threshold invoices do account for all adjustments defined on the subscription. However, these adjustments are applied across the full billing period, not at each threshold. For example, a fixed $10 discount or a $10 minimum charge will be applied once on the final invoice, not at each threshold issuance. ### Will Orb issue a threshold invoice based on credit utilization or overages? Threshold invoices are triggered based solely on the total invoiced amount across all usage-based line items on the subscription. They are not tied to credit burndown, overages, or any individual price component. ### Is there a limit to the number of threshold invoices per billing period? No. There is no enforced limit to the number of threshold invoices Orb may issue for a subscription in a billing period. ### Can I configure a threshold invoice to trigger just once per subscription? No. Threshold invoicing is always based on the total accrued charges and will trigger every time the threshold is reached. However, you can modify the threshold mid-period using the [Update Subscription API](/api-reference/subscription/update-subscription#update-subscription) if you want to prevent further threshold invoices. ## Understanding threshold invoices in the API On a subscription invoice with threshold invoicing enabled, each invoice line item contains a `partially_invoiced_amount` field. This field represents the amount that has already been invoiced for that line item as a result of threshold invoicing and so is not included when calculating the amount due on that invoice. See the guide for [invoice calculations](/invoicing/invoice-calculations) and the step detailing previously invoiced amounts. # Migrating from Stripe Billing Source: https://docs.withorb.com/migration-guides/stripe-billing Orb supports a seamless migration from Stripe Billing, whether you're using flat-rate subscriptions, usage-based pricing, pricing on seats, or you've implemented custom billing logic. This guide walks through the migration process step by step. Orb is designed to minimize engineering lift during migration by supporting historical data backfills, real-time usage ingestion, and integrations with Stripe Payments for collections. Unlike other billing providers, Orb is [fundamentally architected](/architecture/query-based-billing) to support event backfills, which makes it easy to replay historical usage and ensure that your billing setup is accurate before migrating to Orb. ## Overview A typical Stripe Billing migration includes: 1. Modeling your product catalog in Orb 2. Importing customers and subscriptions 3. Backfilling historical usage data 4. Sending real-time usage events 5. Configuring invoicing and collections 6. Validating results and cutting over *Note:* Orb can operate in parallel with your existing Stripe Billing implementation while you validate data and invoices. ## Step 1: Define pricing in Orb Use the dashboard or API to define your product catalog in Orb. This includes: * *[Items and plans](/core-concepts#plan-and-price)*: Representing your sellable products and how you package them. * *Prices and adjustments*: Defining how customers are charged (e.g. flat fees, usage-based charges, discounts, minimums) * *Plan configurations*: Supporting logic such as adjustments over many prices, prepaid allocations, and invoicing settings such as your net terms. ## Step 2: Import customers and subscriptions Customers and their active subscriptions can be imported via the API or dashboard. * Use the [`POST /customers`](/api-reference/customer/create-customer) endpoint to create Orb customers. Set the `external_id` to the customer ID in your system. * Use the [`POST /subscriptions`](/api-reference/subscription/create-subscription) endpoint to define each customer’s active plan and billing cycle. We recommend using a historical `start_date` on the subscription to preserve contract timelines and usage aggregation. ## Step 3: Backfill historical usage (optional) If you want Orb to generate accurate invoices or analytics based on prior usage data, you can backfill events. Ensure that your account is not set to issue invoices before backfilling data, as issued invoices will not be recalculated. 1. Export historical usage from Stripe or your internal systems 2. Format events using Orb’s ingestion schema 3. Use the official backfill script to upload events 4. Close the backfill, which will trigger invoice recomputation to reflect the backfilled usage. *Note:* Backfills support full idempotency and delayed event ingestion, making it safe to replay usage as needed. ## Step 4: Send live usage events Send real-time usage events to Orb using the [ingestion endpoint](/api-reference/event/ingest-events). Orb’s event ingestion system supports: * Out-of-order events * Backdated timestamps * Custom properties for analytics and aggregation * High-throughput ingestion You can continue using your existing Stripe Billing implementation while sending events to Orb in parallel for validation. ## Step 5: Set up invoicing and payments Orb can handle invoice generation and optionally integrate with Stripe for payments. You can configure: * Invoice frequency and timing * Line item formatting and grouping * Webhooks for invoice creation and payment updates * Stripe Payments integration for collections See: [Invoices Overview](/invoicing/introduction) ## Step 6: Cut over to Orb Once all customer data, subscriptions, and usage events are validated: 1. Stop creating subscriptions in Stripe Billing 2. Transition to using Orb as the source of truth for metering and invoicing 3. (Optional) Keep Stripe Payments as your payment processor Orb customers often use Stripe for payments, while Orb takes over all aspects of metering, pricing, and billing logic. ## Frequently asked questions ### Can I run Stripe Billing and Orb in parallel? Yes. Many customers send usage to both systems during testing to compare invoice output and validate pricing changes. ### Will I lose access to my Stripe payment or tax setup? Will customers need to re-input their payment instruments? No. Orb integrates directly with Stripe Payments — your customer records, tax configurations, and payment methods remain intact. ### How long does migration take? A typical migration takes 2 weeks, depending on the complexity of your pricing model and historical usage data. ### What if I have discounts, credits, or prepaid balances? Orb natively supports discounts, prepaid usage, minimums, and credit-based models. These can be configured via the dashboard or API. # Next steps Source: https://docs.withorb.com/next-steps You've built a complete usage-based billing workflow on Orb—from event ingestion through metric definition, pricing configuration, subscription creation, and invoice generation. ## For product teams * **Test pricing changes safely** — [Run simulations](/simulations/introduction) to model revenue impact before deploying. See exact invoice previews across your customer base. * **Set up usage alerts** — [Configure usage alerting](/usage-alerting/overview) to trigger notifications when customers hit thresholds. Power product-led growth workflows. ## For finance teams * **Automate revenue recognition** — [Configure revenue recognition](/revenue-reporting/revenue-recognition) rules and export data for financial reporting and compliance. * **Handle enterprise deals** — [Configure backdated pricing](/enterprise/enterprise-billing) for contracts with retroactive terms. [Set up prepaid credits](/product-catalog/prepurchase) for upfront commits. * **Export billing data** — [Connect data exports](/data-exports/introduction) to your data warehouse for analytics and forecasting. ## For engineering teams * **Build custom metrics** — [Define advanced SQL metrics](/extensibility/advanced-metrics) for precise billing logic. Filter, aggregate, and transform events to match your pricing model. * **Integrate webhooks** — [Set up webhooks](/integrations-and-exports/webhooks) for real-time notifications when invoices are generated or subscriptions change. * **Understand the architecture** — Read the deep dives on [query-based billing](/architecture/query-based-billing) and [diff-based subscriptions](/architecture/billing-architecture) to understand how Orb works under the hood. * **Scale event ingestion** — [Move to high-throughput ingestion](/events-and-metrics/high-throughput-ingestion) for workloads handling millions of events per second. ## Keep exploring * [API Reference](/api-reference) — Complete API documentation * [Core concepts](/core-concepts) — Deep dive into Orb's data model # Numeral Source: https://docs.withorb.com/numeral # About ## **Geographic coverage** US (all states) + 70-80+ countries for VAT/GST (Pro plan) # Setup ## **Prerequisites** 1. A Numeral account with tax engine access. 2. A Numeral API key (test or production). 3. Your business configured in Numeral with the appropriate tax settings. ## **Connecting your account** 1. Create a Numeral account at [dashboard.numeralhq.com/users/sign-up](http://dashboard.numeralhq.com/users/sign-up) and check the box indicating interest in the tax engine. 2. In Numeral, go to **Developers > API keys > Create** a test API key, and save it. 3. In Orb, navigate to **Settings > Taxes** and select “Connect to Numeral.” 4. Enter your Numeral API key and click Save. Use a Numeral test mode key for Orb test mode, and a production key for Orb live mode. ## **Setting up tax codes** The integration supports Numeral's product categorization system for tax classification. For each Item in Orb, select the appropriate Numeral product category. If none is specified, Orb defaults to GENERAL\_MERCHANDISE. Common categories. The [complete list of categories is available in Numeral's docs](https://docs.numeral.com/essentials/product-categories). # **Sample request and response payload** *The example below is illustrative of the shape Orb sends to and receives from Numeral today, based on Orb's tax provider processors and test fixtures. Exact field names and structure can change as Numeral versions its API — confirm against current behavior before relying on it for integration work.* ### ***Request (Orb → Numeral)*** ```text theme={null} { "customer": { "address": { "address_line_1": "123 Test St", "address_city": "San Francisco", "address_province": "CA", "address_postal_code": "94105", "address_country": "US", "address_type": "billing" } }, "order_details": { "customer_currency_code": "USD", "tax_included_in_amount": false, "line_items": [ { "amount": 10000, "quantity": 1, "reference_line_item_id": "line-item-456", "reference_product_id": "product-123", "product_category": "SAAS_GENERAL" } ], "automatic_tax": "auto" }, "origin_address": { "address_country": "US" }, "metadata": { "orb_invoice_id": "" } } ``` ### ***Response (Numeral → Orb)*** ```text theme={null} { "id": "calc-123", "testmode": true, "tax_included_in_amount": false, "total_tax_amount": 875, "total_amount_excluding_tax": 10000, "customer_currency_code": "USD", "line_items": [ { "tax_amount": 875, "line_item_id": "line-item-456", "amount_excluding_tax": 10000, "amount_including_tax": 10875, "product": { "reference_line_item_id": "line-item-456" }, "tax_jurisdictions": [ { "jurisdiction_name": "California", "tax_rate": 0.0875 } ] } ] } ``` ## **Reporting tax for filing** After calculating tax, Orb separately reports the transaction to Numeral once the invoice is issued or finalized, via POST `/tax/transactions`. Orb stores the returned Numeral transaction ID for future reference. ### ***Request (Orb → Numeral, reporting)*** ```text theme={null} { "calculation_id": "calc-123", "reference_order_id": "", "transaction_processed_at": 1661990400, "metadata": { "orb_invoice_id": "", "orb_invoice_number": "" } } ``` ### ***Response (Numeral → Orb, reporting)*** ```text theme={null} { "id": "tr_123456789", "object": "tax.transaction", "calculation_id": "calc-123", "reference_order_id": "", "transaction_processed_at": 1661990400, "customer_currency_code": "USD", "filing_currency_code": "USD", "line_items": [] } ``` # Overview Source: https://docs.withorb.com/overview Orb handles usage-based, seat-based, and hybrid billing. From simple subscriptions to complex enterprise contracts, test pricing changes before deploying, backdate amendments without manual fixes, and process millions of events per second. Built on our query-based architecture for flexibility and scale. Explore Orb's [demo environment](https://demo.withorb.com?vertical=cloud_infra) with sample data across common use cases. To get started, [contact sales](https://www.withorb.com/contact-us). ## Any pricing model Bill for API calls, compute, tokens, agents. Common for infrastructure and AI. Combine seats with usage overages. Common for B2B SaaS platforms. Support trials, access control, and enterprise commits with credits. Backdate amendments, shared credits, complex multi-year deals. ## Learning paths Complete billing workflow in 30 minutes Learn what makes query-based architecture different ## Key features * **[Simulations](/simulations/introduction)** - Test pricing changes against historical usage before deploying. * **[Backfills & corrections](/architecture/query-based-billing)** - Late data handled safely. Orb recalculates affected invoices automatically. * **[Hosted rollups](/events-and-metrics/high-throughput-ingestion)** - Handle 1M+ billing events per second in production deployments. * **[Real-time visibility](/product-catalog/usage-visibility)** - Track accrued revenue before invoices. Forecast and spot expansion. * **[Custom SQL metrics](/events-and-metrics/construct-metrics)** - Build complex billing metrics with full SQL control. * **[Usage alerting](/usage-alerting/overview)** - Trigger alerts at usage thresholds to power product-led growth. * **[Revenue recognition](/revenue-reporting/revenue-recognition)** - Track recognized and deferred revenue with exports for financial systems. ## Resources Events, Metrics, Plans, Subscriptions, Invoices Complete docs with SDKs for Python, Node, Ruby, Go Move from Stripe Billing, Metronome, Chargebee # Overview Source: https://docs.withorb.com/overview-1 Orb integrates third-party tax providers to streamline calculation on Orb invoices, and ensure compliance for your business. Today, we support: * Anrok * Avalara (AvaTax) * Numeral * Sphere * Stripe Tax * TaxJar These providers maintain tax rates per nexus or jurisdiction, so you don't have to track changes to tax code or your own tax obligations as your sales evolve. They also handle tax filing and reporting, and connect directly to Orb billing data to reduce reconciliation and reporting costs. Orb's tax integrations only work with Orb Invoicing. If you sync invoices to an external invoicing provider such as Stripe Invoicing or NetSuite, tax comes from that provider's settings. # **How your billing (Orb) and tax provider interact** Orb is the system of record for the invoice and the source of the data that feeds a tax calculation. Your tax provider is the source of truth for tax law, rates, and filings. Orb does not calculate tax or determine what you owe — it packages up invoice data, sends it to your configured provider, and reflects the result back on the invoice.  ## **Orb is responsible for** | **Area** | **Details** | | -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Providing tax calculation inputs |
  • What was sold – invoice line items, amounts, adjustments, and currency
  • Customer information – customer shipping/billing addresses, tax IDs, exemption status, and product tax code mappings
| | Presenting tax on the invoice |
  • Applying tax amounts returned by your provider to the correct line items and totals
  • Displaying tax amounts and your merchant tax ID on the invoice portal and PDF
| | Reporting committed tax amounts |
  • Sending finalized tax amounts back to your tax provider for reporting and filing
| ### **Your tax provider is responsible for** | **Area** | **Details** | | ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | Tax calculation |
  • Determining nexus and jurisdiction obligations
  • Applying the correct tax rate(s) per line item based on tax code or category
| | Returning tax outputs |
  • Calculating the tax amount per line item and returning it to Orb
| | Exemptions and reverse charge |
  • Applying exemption certificates and reverse-charge treatment for valid customer tax IDs
| | Filing and remittance |
  • Recording and reporting committed transactions for per-jurisdiction filing and remittance
| | Audit trail |
  • Maintaining the records required by tax authorities
| ### You are responsible for | **Area** | **Details** | | ------------- | ------------------------------------------------------------------------------- | | Configuration |
  • Mapping tax codes and items between Orb and your tax provider
| # **What Orb sends and receives** Each tax provider expects a different payload shape and field naming convention, but the data Orb sends follows the same general pattern across all six integrations. ### **What Orb sends for tax calculation (request)** 1. Customer identity and address — the Orb customer ID and the shipping address (falling back to billing address) used to determine jurisdiction. Where a provider's calculation supports specifying origin/seller context (e.g. Avalara's ShipFrom, Numeral's origin\_address), that context reflects your merchant tax registration. 2. Line items — one entry per invoice line item, each with its amount, quantity where applicable, and an external tax code, product ID, or category configured on the Orb Item. 3. Currency and dates — the invoice currency and the accounting/reporting date used for rate lookups. 4. Tax ID and exemption context — the customer's tax ID and/or tax-exempt status, for providers that use it to apply reverse charge or exemption logic. 5. Metadata — identifiers linking the transaction back to the originating Orb invoice and line items, so the response can be reconciled. ### **What Orb receives for tax calculation (response)** 1. A tax amount per line item, plus an invoice-level total tax amount. 2. Jurisdiction and rate detail per line item (e.g. jurisdiction name, tax rate, tax name), for providers that return it — Orb surfaces this in the dashboard for auditing. 3. Any not-taxed or exemption reasons the provider determines for a given line item. Orb applies the per-line-item tax amounts back onto the invoice as soon as the calculation call returns. ### **Committing / reporting on the taxable transaction** For most providers, Orb makes two separate calls per invoice: 1. A calculation call (described above) to compute the tax amount, and  2. A second reporting or commit call once the invoice is issued or finalized to register the transaction with the provider so it can be filed with a tax authority.  This second step is generally tied to invoice issuance/finalization, not to when the invoice is paid — Orb's reporting logic (and its backfill tooling) targets invoices with status issued. **Provider-specific notes** * **Anrok, TaxJar, Stripe Tax, and Numeral**: Orb makes a second create/report-transaction call referencing the original calculation, once the invoice is issued or finalized. * **Avalara** is the one true “commit” flow: Orb creates the transaction during calculation, then later calls commit\_transaction on that same document. This is gated by the Commit tax setting and fires specifically when the invoice transitions from issued to paid — not at issuance like the others. * **Sphere** has no separate reporting or commit call. Its “report tax” step just re-runs the same calculation call, and Sphere instead receives updates via webhook. Tax Diagram # **Configuring customers for tax calculation** **Address requirements** Each taxable customer is required to have a valid **shipping address** in Orb. If none is present at the time of invoice issuance, Orb uses the billing address. If the address is invalid or missing, Orb fails invoice issuance and notifies you in the dashboard. **Updating customer addresses** [**Customer addresses can be set via the API**](https://docs.withorb.com/api-reference/customer/update-customer) or via the dashboard from the customer details page. **Tax-exempt customers** For a customer that qualifies for a tax exemption, create a Certificate for the customer in your tax provider dashboard, using the same customer ID as in Orb so the exemption is appropriately applied. **Exempt from automated tax (Orb customer setting)** Orb has a customer-level operational control to skip tax calculation for that customer or account. This is not a legal determination, and is typically used when taxes are handled outside of Orb, such as in a marketplace, an external invoicing system, or another tax engine. **Customer tax IDs** If a customer has a tax ID configured in Orb, Orb sends that ID to the tax provider when calculating and reporting tax. The tax provider can use it to determine whether the transaction is subject to a reverse charge for eligible VAT countries. The customer tax ID [can be set via the API](https://docs.withorb.com/api-reference/customer/update-customer) or via the dashboard from the customer details page. # **Merchant and customer tax identifiers** Orb supports both customer and merchant tax identifiers. This information is displayed on the invoice, and passed to the tax provider as an input. ### **Customer tax identifiers** If your customer has a tax ID that's required to be displayed on an invoice, [set the Customer Tax ID via the API](https://docs.withorb.com/api-reference/customer/update-customer#body-tax-id-one-of-0) or from the customer details page in the dashboard. Tax Customer Ids **A customer’s tax ID is final at the time of invoice issuance.** Any draft invoices will display the customer's current tax ID. Any issued invoice saves the customer's tax ID at the time of issuance, which cannot later be changed. To update this information, the invoice must be voided and re-issued. Contact Orb Support for assistance. ### **Merchant tax identifiers** You can add your company's own tax registration numbers in **Settings > Invoices**. These merchant tax IDs appear in the invoice “From” section and on invoice and credit note PDFs when the recipient's country or region matches the tax ID's **Applies to** setting. This is an Orb-side capability, so it applies uniformly whether you're connected to Anrok, Avalara, Numeral, Sphere, Stripe Tax, or TaxJar. Orb supports up to 50 company tax IDs per account, and cross-border invoices display both the merchant and customer country names alongside the addresses so the tax context is clear. Tax Merchant Ids # **Tax handling for reversals (credit notes and voids)​** * **Credit notes.** When a credit note is issued on a taxable invoice, Orb calculates the tax adjustment proportionally to the original transaction and reports it to your tax provider automatically. This ensures your refund activity is reflected accurately in your compliance records and maintains a complete audit trail for tax authorities. * **Voids.** If a taxable invoice is voided, Orb treats that differently from a credit note: the void removes the original tax impact rather than recording a refund, and Orb sends the appropriate void or reversal to keep the tax provider reconciled with Orb. * In both cases, the tax reversal references the customer information provided on the original invoice. # Applying adjustments and discounts Source: https://docs.withorb.com/product-catalog/adjustments Adjustments are modifications that can alter invoice totals or individual line items to implement pricing strategies like discounts, minimums, and maximums. They provide flexible control over billing calculations and can be applied at different scopes within your pricing structure. ## Types of adjustments Orb supports several types of adjustments that can be applied to modify billing calculations: ### Discounts **Amount discounts** subtract a fixed amount from the subtotal. For example, a \$100 discount reduces the charge by exactly \$100. **Percentage discounts** reduce the subtotal by a percentage. For example, a 20% discount on a \$500 charge results in a \$400 final amount. **Usage discounts** reduce the billable quantity before applying the pricing function. For example, "100 units off" reduces the billable quantity by 100 units before calculating the charge. ### Minimums and maximums **Minimums** ensure that the adjusted subtotal doesn't fall below a specified amount. For example, "at least \$50" guarantees that the final charge will be no less than \$50, regardless of usage or other discounts. **Important**: Minimum adjustments are not applied to invoices that contain only fixed fees. This rule ensures that minimums don't inappropriately apply to invoices consisting entirely of fixed fees, such as the first in-advance invoice for a subscription without usage-based charges. **Maximums** cap the adjusted subtotal at a specified amount. For example, "at most \$1000" ensures that the final charge will not exceed \$1000, even with high usage. ## Use cases for adjustments Adjustments enable a wide range of billing scenarios: * **Promotional discounts**: Offer temporary percentage or amount discounts to attract new customers or reward loyalty * **Volume commitments**: Apply minimums to ensure customers meet spending commitments * **Usage caps**: Use maximums to provide predictable billing for customers with variable usage * **Employee discounts**: Create consistent discount structures for internal users * **Custom pricing**: Implement negotiated terms for enterprise customers * **Feature add-ons**: Apply targeted discounts to specific services or products * **Seasonal promotions**: Time-limited discounts for marketing campaigns ## Creating adjustments Adjustments can be created through several methods: ### Plan-level adjustments Adjustments can be configured directly on [plans](/product-catalog/build-catalog) and will apply to all subscriptions using that plan. These are useful for standard pricing policies that should apply consistently across customers. ### Subscription-level adjustments Individual adjustments can be added to specific [subscriptions](/product-catalog/creating-subscriptions) using the [subscription price intervals API](/api-reference/price-interval/add-or-edit-price-intervals). This allows for customer-specific pricing without modifying the underlying plan. See [editing subscriptions](/product-catalog/editing-subscriptions) for more details on how to add, remove, and change adjustments on subscriptions. ### Coupons [Coupons](/api-reference/coupon/list-coupons) are a special way to create reusable adjustments that come with a redemption code and usage tracking. When a coupon is redeemed, Orb automatically creates the corresponding adjustment and adjustment interval, applying it to the subscription. Coupons always create **subscription-scoped adjustments**, meaning they apply to all prices on the subscription by default. This means that any new prices added to the subscription (such as through plan changes or add-ons) will automatically be included in the coupon's discount. **Key differences from direct adjustments:** * **Redemption code**: Coupons have a unique code that customers can use to apply the discount * **Usage tracking**: Coupons track how many times they've been redeemed and can have redemption limits * **Reusability**: The same coupon can be applied to multiple subscriptions until it reaches its redemption limit * **Duration control**: Coupons can have time-limited effectiveness by creating adjustment intervals with specific start and end dates Coupons are particularly useful for promotional campaigns, employee discounts, and self-serve signup flows where customers need to apply discounts themselves. **Redeeming coupons:** Coupons can be redeemed using the `redemption_code` when: * [Creating a subscription](/api-reference/subscription/create-subscription) - Apply the coupon from the start of the subscription * [Scheduling a plan change](/api-reference/subscription/schedule-plan-change) - Apply the coupon when transitioning to a new plan **Adjustment behavior during subscription changes:** All adjustments, including those created by coupons, behave differently during mid-cycle changes: * **Percentage discounts** continue to apply proportionally to new charges * **Amount discounts** do not apply to mid-cycle upgrade invoices to prevent over-discounting **Example: Mid-cycle upgrade with adjustment** A user upgrades their seats with 5 days remaining in a 30-day billing cycle from 1 seat to 2 seats on a plan that costs \$60 per month per seat. A prorated charge of \$10 is added in a new invoice for the upgrade. *Amount Discount (\$20):* * Initial invoice: \$40 invoice total = \$60 for the 1 seat - \$20 coupon * Upgrade invoice: \$10 prorated charge for 5 days with 2 seats. The discount does not apply to the second invoice. * Total cost: \$50 (\$40 initial + \$10 upgrade). *Percentage Discount (50%):* * Initial invoice: \$30 invoice total = \$60 for the 1 seat - 50% discount * Upgrade invoice: \$5 invoice total = \$10 prorated charge for seat 2 - 50% discount * Total cost: \$35 (\$30 initial + \$5 upgrade) Amount discounts don't apply to upgrade invoices to prevent scenarios where the upgrade would be free or over-discounted. ## Adjustment targeting with filters Orb uses a modern filter-based system to target adjustments precisely. This system consolidates multiple targeting dimensions into a unified model that supports price-, item-, and subscription-level scopes. ### Filter structure Filters use the following format: ```json theme={null} { "field": "price_id" | "item_id" | "price_type", "operator": "includes" | "excludes", "values": [...] } ``` When multiple filters are specified, they are ANDed together, meaning line items must satisfy all filters to be included in the adjustment. ### Targeting options **Subscription-scoped**: Apply to all prices on a subscription by using an empty filters array or `applies_to_all: true`. **Item-scoped**: Target specific items using `item_id` filters. This is useful for applying discounts to particular products or services. **Price-scoped**: Target specific [prices](/product-catalog/price-configuration) using `price_id` filters. This provides granular control over which charges receive adjustments. **Price type-scoped**: Target prices by type using `price_type` filters with values like: * `usage`: Any usage-based price attached to a billable metric * `fixed_in_advance`: Fixed prices that bill in advance * `fixed_in_arrears`: Fixed prices that bill in arrears The `price_type` field on an adjustment is a shortcut for creating `price_type` filters. You cannot use both the `price_type` field and explicit filters on the same adjustment. Additionally, the `fixed` and `in_arrears` values are only valid for the `price_type` field (where they resolve to exclusion filters) but cannot be used as values in `price_type` filters themselves, which only support the inclusion values listed above. ### Currency filter requirements **Currency filter behavior**: For all adjustment types except percentage discounts, you must specify a currency filter. How you express it depends on your targeting approach: - **Shortcut fields** (`applies_to_item_ids`, `applies_to_price_ids`, `applies_to_all`): Use the top-level `currency` field * **Full filters**: Include a currency filter in the `filters` array (the top-level `currency` field is not allowed) ### Filter examples You should use only one of `applies_to_all`, `applies_to_item_ids`, or `applies_to_price_ids` in a single adjustment. Or, use `filters` instead to express more complex targeting logic. **Subscription-level discount**: ```json theme={null} { "adjustment": { "applies_to_all": true, "currency": "USD", "is_invoice_level": true, "adjustment_type": "percentage_discount", "percentage_discount": "0.1" } } ``` **Item-scoped maximum**: ```json theme={null} { "adjustment": { "applies_to_item_ids": ["item_storage", "item_compute"], "currency": "USD", "is_invoice_level": true, "adjustment_type": "maximum", "maximum_amount": "1000" } } ``` **Advanced filtering**: ```json theme={null} { "adjustment": { "filters": [ { "field": "item_id", "operator": "includes", "values": ["item_premium"] }, { "field": "price_type", "operator": "includes", "values": ["usage"] } ], "is_invoice_level": true, "adjustment_type": "percentage_discount", "percentage_discount": "0.15" } } ``` ### Benefits of filter-based targeting * **Consolidated targeting**: Express complex targeting rules in a single adjustment object * **Durability**: Item and subscription scopes persist across [plan migrations](/product-catalog/price-changes), even when price IDs change * **Flexibility**: Combine multiple targeting dimensions for precise control * **Backward compatibility**: Legacy `applies_to_price_ids` continues to work alongside the new system ## Modifying adjustments ### Adjustment intervals Adjustments are applied through adjustment intervals, which define the time period during which an adjustment is active. You can: * **Add adjustments**: Create new adjustment intervals starting immediately or in the future * **Remove adjustments**: End existing adjustment intervals at a specified date * **Modify adjustments**: Replace existing adjustments by ending the current interval and creating a new one ### Timing behavior Adjustment intervals determine whether adjustments apply to invoices based on the invoice date and the adjustment's time bounds: * **In-arrears fees**: Adjustment interval is inclusive of its end bound * **In-advance fees**: Adjustment interval is inclusive of its start bound * **Mixed fees**: Interval is inclusive on both bounds **Backdated adjustments and invoice reissuance:** When you create adjustment intervals with start dates in the past, Orb will automatically reissue any affected invoices to ensure they reflect the correct pricing. This happens because Orb maintains consistency between subscription state and invoice state using its [diff-based billing engine](/architecture/billing-architecture). For example, if you add a 10% discount effective from the beginning of the current month and invoices have already been issued for that period, Orb will: 1. Calculate what the invoices should have been with the discount applied 2. Void the original invoices 3. Issue new invoices with the correct discounted amounts This automatic correction ensures that your billing records always accurately reflect your intended pricing policies, even when adjustments are applied retroactively. ### API operations Use the [subscription price intervals API](/api-reference/price-interval/add-or-edit-price-intervals) to: ```json theme={null} { "add_adjustments": [ { "adjustment": { "applies_to_all": true, "adjustment_type": "percentage_discount", "percentage_discount": "0.2" }, "start_date": "2024-01-01", "end_date": "2024-03-31" } ] } ``` ## Adjustments and plan version migrations When you perform [plan version migrations](/product-catalog/price-changes#migrating-subscriptions-to-a-new-version), adjustments behave differently depending on how they were originally configured. Understanding this behavior is crucial for maintaining consistent pricing policies across plan changes. ### Plan-level adjustments **Automatic remapping**: Adjustments configured at the plan level automatically adapt to plan version changes. When a price is edited in a new plan version (creating a new price ID), Orb automatically remaps the adjustment to apply to the replacement price. **Example**: If you have a plan-level 10% discount that applies to all prices, and you edit a usage-based price in a new plan version, the discount will automatically apply to the new price ID without any manual intervention. ### Subscription-level adjustments with price IDs **No automatic remapping**: Adjustments added directly to subscriptions using `applies_to_price_ids` cannot be automatically remapped during migrations. Since these adjustments target specific price IDs, they become "orphaned" when those prices are replaced with new IDs. **Migration protection**: To prevent unintended consequences, Orb will fail the migration for subscriptions that have ad-hoc adjustments targeting prices that are being modified. This ensures you don't accidentally lose discount policies or create billing inconsistencies. **Manual intervention required**: You'll need to manually update these adjustments before or after the migration to target the new price IDs. ### Filter-based adjustments advantage Filter-based adjustments provide superior durability during plan migrations because they express intent rather than targeting specific price IDs: **Subscription-scoped filters** (`applies_to_all: true` or empty `filters` array): * Continue to apply to all prices on the subscription, including new ones added during migration * No manual intervention required **Item-scoped filters** (`item_id` targeting): * Continue to apply to prices associated with the same items, even when price IDs change * Automatically include new prices for the same items **Price type-scoped filters** (`price_type` targeting): * Continue to apply to prices of the same type (e.g., all usage-based prices) * Automatically include new prices of the matching type **Example of filter durability**: ```json theme={null} { "filters": [ { "field": "item_id", "operator": "includes", "values": ["storage_item"] } ] } ``` This adjustment will continue to apply to all storage-related prices, regardless of plan version changes or new price IDs. ### Best practices for migrations 1. **Use filter-based targeting** for new adjustments to ensure they survive plan migrations 2. **Review subscription-level adjustments** before performing migrations to identify potential conflicts 3. **Consider converting legacy price ID-based adjustments** to filter-based ones before major plan changes 4. **Test migrations on a small subset** of subscriptions to verify adjustment behavior For advanced functionality to automatically apply existing adjustments to replacement prices during migrations, please reach out to Orb support for access to private preview features. ## Invoice calculations Adjustments are applied during invoice calculation in a specific order to ensure consistent and predictable results. The calculation process follows these steps: 1. **Determine Line Item Quantity** 2. **Apply the Pricing Function** - Calculates the subtotal based on the quantity 3. **Apply Line Item Level Adjustments** in order: usage discounts, amount discounts, percentage discounts, minimums, maximums 4. **Apply Multi-Price/Invoice Level Adjustments** in the same order 5. **Apply Prepaid Credits** (in-arrears charges only) 6. **Apply Overage Conversion** (if applicable) 7. **Subtract Previously Invoiced Amounts** (for threshold billing) 8. **Apply Line Item Tax** ### Adjustments and custom currencies For prices configured with custom currencies (virtual pricing units), adjustments are applied in the price's original currency before any overage conversion takes place. This means: * **Adjustments operate in the custom currency**: A discount on a price denominated in "API credits" will reduce the credit amount before conversion to real currency * **No adjustments on overage charges**: You cannot apply adjustments specifically to the overage portion after prepaid credits are exhausted * **Conversion happens after adjustments**: The adjusted amount is then converted to the real-world currency using the configured conversion rate **Example**: If you have a price in "Database credits" with a 10% discount and the customer uses 1000 credits: 1. Base charge: 1000 Database credits 2. Apply 10% discount: 900 Database credits 3. Apply prepaid credits: Subtract available credits from the 900 4. Convert overage: Any remaining credits convert to USD at the configured rate ### Adjustment distribution When adjustments apply to multiple prices, they are distributed proportionally based on each line item's share of the total subtotal, except for minimums which are distributed evenly. For detailed information about how adjustments factor into invoice calculations, including examples and edge cases, see the [Invoice calculations](/invoicing/invoice-calculations) documentation. # Configuring plans Source: https://docs.withorb.com/product-catalog/build-catalog Since Orb automates recurring billing, customers in Orb maintain [subscriptions](/core-concepts#subscription) to your product. Subscriptions are associations between a customer and a plan. ## Plan A plan is a standardized set of pricing terms, and corresponds to a specific tier of functionality within your product. Depending on the nature of your business, many customers might be assigned to the same plan (e.g. when a visitor upgrades to the “Pro” plan listed on your website), and some customers may have their own one-off plans (e.g. the “Enterprise” offering with negotiated terms and feature set). Create plan flow ### Component prices A plan is a collection of prices, where each price corresponds to a charge you display on your customer’s invoice (also known as a line item). At their core, prices need to be configured with a *cadence* (how often to charge), and a pricing function (how much to charge for a given quantity). Prices can be categorized as: * Usage-based prices, which are always attached to a single [billable metric](/core-concepts#metric). The billable metric determines the quantity, and the price determines the amount to charge based on its pricing model. * Recurring fixed prices, which simply apply a fixed charge on a cadence. A common example is a platform access fee, or a per-user fee where the number of users is known up-front. ### Standard price models | Price model | Description | Example | | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Unit | A simple, linear price that scales with the total units of usage. | \$0.80 per API request | | Tiered | The cost of a unit depends on the *tier range* it falls into. Normally, each incremental unit will get cheaper as usage hits reach higher tiers. | \$5 per GB for the first 100GB, \$10 per GB thereafter | | Bulk | The total number of units determines the cost of all units. Typically, reaching a higher tier value will decrease the price per unit. | \$0.20 per page processed, discounted to \$0.10 per page if 10,000 pages consumed | | Package | Units are rounded up to a specified granularity level for pricing. | \$1 per 100 asynchronous jobs | | Dimensional pricing | Configure prices that vary based on multiple event properties or dimensions. | \$0.05/GB for `cold storage` / `us-east-1`
\$0.08/GB for `hot storage` / `us-east-1`
\$0.03/GB for `cold storage` / `us-west-1`
\$0.08/GB for `hot storage` / `us-west-1` | ### Custom prices Orb's extensible platform approach makes it simple to deploy custom prices onto the platform using Python, so you're not limited by Orb's standard price models. Pricing models taking a flexible number of parameters are fully configurable via the Orb UI. See [custom pricing platform](/extensibility/custom-pricing) for how these prices are set up. ### Adjustments Orb provides rich support for creating adjustments like discounts, minimums, and maximums that modify your pricing. When creating an adjustment, you specify the set of prices that it applies to, allowing you to create adjustments that apply at the plan level (applying to many or all of the plan's prices), as well as for an individual price (a single line item on your invoice). #### Discounts Orb allows the creation of discounts to decrease the amount charged for a price or set of prices. Discounts can optionally expire after a certain number of billing periods, which make them ideal for limited-time contract terms. This prevents you from having to manually orchestrate a plan change for your subscription to provide new pricing or to remove a discounted term. Orb supports multiple discount models: * **Usage-based discounts**, which decrease the quantity of a price. You can this discount to represent a free allocation, such as decreasing the number of units processed by 300. Usage-based discounts are compatible with all pricing models, not just simple unit prices. * **Amount discounts**, which decrease the line item or invoice total by a certain flat dollar amount. A custom term that decreases a customer’s invoice by \$100 each month would use this discount model. * **Percentage-based discounts**, which decrease the invoice or invoice line item by a percentage of their subtotal. This can be used to provide 20% off recurring storage costs for the first year of a subscription. #### Minimums In addition to discounts, you may want to represent commitments to a dollar amount in Orb by using minimums. Setting a minimum amount ensures that the total of the invoice or invoice line item is at least a specific threshold. If the minimum threshold isn’t met, Orb will automatically add a line with the difference between the minimum threshold and current subtotal. Just like discounts, minimums can optionally expire after a set number of billing periods. #### Maximums Additionally, you can represent spend caps for a plan by creating maximums for a price or set of prices. When you set a maximum, Orb ensures that the total charge for the prices included in the maximum do not exceed the specified threshold. If the calculated charge would surpass the maximum, Orb automatically applies a discount to bring the total down to the maximum amount. Like discounts and minimums, maximums can be configured to expire after a certain number of billing periods. This feature is particularly useful for implementing capped pricing models or for providing temporary upper limits on charges during promotional periods or as part of specific contract terms. ### Plan phases Subscriptions will often have a pre-decided schedule of changes, such as a change in the pricing after a limited period or an increase in the committed spend after a fixed number of months into the subscription. Instead of having to modify a subscription’s plan manually, plan phases provide an automated way to encode these steps into the definition of the plan itself. By default, each plan has a single phase, which means that no changes are scheduled to pricing terms. Configuring plan phases simply requires specifying a duration in billing terms, and optionally a description for what the phase represents (e.g. initial 6-month commitment). A phase switch always happens at the end of a billing period, which means that a single subscription invoice will not contain charges from prices in multiple phases. Note that the last phase of any plan is an evergreen phase, which signifies that it will continue indefinitely until the end of the subscription. ### Trials A subscription can start with a free trial, during which your users can use the product without paying. A trial inherits the first phase of your plan’s pricing configuration, excluding any fixed fees specified. For example, if you usually charge a \$500 platform access fee in-advance every month and a fee for storage charges, the trial period will only track storage charges. Even during a trial, Orb provides complete visibility into your customer’s usage and the charges that would normally be associated with them. A trial is configured with a duration in days, which represents the time period before the customer starts paying for their incurred usage. Orb also allows you to specify a maximum usage amount in dollars, which lets you limit how much consumption is discounted on the trial invoice. At the end of the trial’s duration, Orb will always generate a single invoice that breaks down the usage charges, and includes an additional trial discount line item. The discount line item will be at most the maximum usage amount. Orb also allows you to extend a trial period while a trial is still active, which is often useful as a sales lever to provide your customers more time to try your product. When a trial period is extended, Orb will continue to discount usage charges up to the maximum amount, if configured. ## Billing cycle configuration Prices in Orb must have a billing cycle configuration, which specifies the duration and whether the price recurs. One-time prices produce a single line item with the specified duration (e.g. a 3 month implementation fee). Recurring prices produce a series of line items spaced at the specified duration (e.g. a monthly usage price). Orb exposes a set of standard configurations (e.g. quarterly), but also allows for customization of the billing cycle (e.g. a 478 day one-time fee). ### Invoicing cycle configuration The billing cycle specifies period over which a price should be evaluated, but this may not match the frequency at which invoices should be produced. For example, consider an quarterly tiered price with the following configuration: * 0 - 10: \$1 per unit * Over 10: \$2 per unit If the customer uses 10 units per month, we'd expect to invoice \$50 (10 \* \$1 + 20 \* \$2) over the quarter. This is different from the same tiered price billed *monthly*, where we'd bill a total of \$30 (3 separate \$10 invoices) over the quarter. However, if we set the billing cycle to *quarterly* and the invoicing cycle to *monthly*, we can still bill the correct \$50 while producing an invoice each month (\$10, \$20, \$20). # Creating subscriptions Source: https://docs.withorb.com/product-catalog/creating-subscriptions A subscription represents a Customer's *recurring purchase* of a plan. Subscription detail page ## How to create a subscription You can create a subscription either from the Orb UI or via the [**subscription creation**](#) API endpoint. ### When to choose which path? | **If you need…** | **Choose…** | | :------------------------------------------------------- | :------------------------------- | | Standardized experiences for many customers | **Plan Template** | | Custom rates, terms, or structures for a single customer | **Plan Template with overrides** | ## Subscription lifecycle A subscription's **term** is the length of time which determines its renewal cadence, determined by looking at the maximum cadence among all its component prices. A subscription's **billing period** is the length of time that determines how often invoices are generated, aligned to a bill cycle day. It can be determined by looking at the minimum cadence among all the component prices. | Component prices | Term | Billing period | | ------------------------------------------------------------------------------ | ------- | -------------- | | 2 Monthly usage charges | Monthly | Monthly | | 2 Monthly usage charges, Annual platform fee | Annual | Monthly | | Monthly charge for storage, Quarterly charge for services, Annual platform fee | Annual | Monthly | | Quarterly charge for services, Annual fee | Annual | Quarterly | A subscription's current **status** is a function of its start and end date. | Status | Description | | ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `upcoming` | The subscription's `start_date` is in the future. Sending usage data for the corresponding customer will not be associated with any charges since there is no active subscription. | | `active` | The subscription is currently active, and may accrue charges as usage events are ingested. | | `ended` | The subscription's `end_date` is in the past, likely as a result of a cancellation. | ### Billing cycle alignment By default, monthly subscriptions that are created will be billed on the first of each month, and any in-advance charges (e.g. a platform access fee) will be appropriately pro-rated on the first invoice. This behavior is often desirable because it provides a consistent reference point for internal processes, such as accounting. Orb also allows you to align the billing cycles with the date that the subscription was created. If a subscription was created on the 14th of November and involved a monthly price, this behavior would enable you to consistently invoice the customer on the 14th of each month. Orb recommends that you use this behavior if you'd like to avoid pro-rating any in-advance fees on the first invoice. If the anchored day is not present in the current month (e.g. billing in February for a subscription that started on Jan 31), then the last day of the month is used. If neither of those options are sufficient, Orb supports fully custom billing cycle alignment by specifying a date on which billing cycles are anchored. For example, a quarterly subscription anchored on March 16, 2024 with a start date of October 10, 2023 and an end date of March 16, 2024 would have the following billing periods: * October 10, 2023 - December 16, 2023 * December 16, 2023 - March 16, 2024 Note that the first period will be pro-rated because the start date of the subscription is not aligned with the anchor date (the full billing period would be September 16, 2023 - December 16, 2023). ### Cancellation behaviors A subscription can be cancelled either effective immediately or at the end of its subscription term. If a subscription is cancelled end of term, it will continue to invoice every billing period until the term is exhausted. Orb also allows canceling a subscription effective a *past* date. The following examples illustrate how this is handled, where the cancellation action occurs on the "action date" but the `requested_date` (via the [cancel subscription](/api-reference/subscription/cancel-subscription) endpoint) is the effective date. Note that backdated cancellations are allowed only if there are no paid invoices between the action date and the requested date. In these examples, assume that the subscription bills \$50 monthly up front, and has a usage based fee. | Subscription start date | Cancellation action date | Cancellation effective date | Behavior | | ----------------------- | ------------------------ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 01-01-2022 | 01-15-2022 | 01-13-2022 | Consistent with [prorations for in-advance fees](/product-catalog/subscription-plan-change#prorations-for-in-advance-fees), Orb will generate a balance refund for the unused time for the month of January, and an in-arrears invoice capturing any usage from 01-01 to 01-13. | | 01-01-2022 | 01-15-2022 | 01-01-2022 | Orb will void the invoice issued on 01-01, and generate no further invoices. | | 01-01-2022 | 02-15-2022 | 02-01-2022 | Orb will void the invoice issued on 02-01 which includes the upfront charge for February, and issue a new invoice for 02-01 capturing the usage for the month of January. | When backdating the cancellation of a subscription that includes prepaid credits, the credit consumption for the period between the cancellation action date and the cancellation effective date will be undone. #### How cancellation affects invoices When a subscription is cancelled, Orb automatically reconciles all affected invoices: * **Draft invoices** are adjusted in-place to reflect the new subscription end date. You'll see a draft invoice for charges up to the cancellation date only. * **Issued invoices** after the cancellation date are either voided or receive credit notes for the prorated refund. * **In-advance fees** that were already paid are prorated and the unused portion is added to the customer's balance as a credit. If you use the `immediate` cancellation option on a subscription with in-advance fees, the customer must be eligible to use customer balance. The prorated refund is added to their balance for future invoices. #### End-of-term cancellation uses the longest billing cadence When cancelling a subscription at the "end of term," the term is determined by the **longest billing cadence** among all prices in the subscription—not the shortest. For example: | Subscription prices | "End of term" cancellation date | | -------------------------------------- | ------------------------------- | | Monthly usage + monthly platform fee | End of current month | | Monthly usage + quarterly platform fee | End of current quarter | | Monthly usage + annual commitment | End of current year | This ensures customers complete their full commitment period for all prices before the subscription ends. #### Pending plan changes and cancellation If a subscription has a pending plan change scheduled, and you cancel the subscription at a date **before or at the plan change date**, the pending change is automatically cancelled. You cannot have a plan change take effect after a scheduled cancellation. #### Unscheduling cancellations If you have scheduled a future cancellation, you can unschedule it using the [unschedule subscription cancellation](/api-reference/subscription/unschedule-subscription-cancellation) endpoint. **Uncancellation is a lossy operation.** When you unschedule a cancellation: - Price intervals that were cut short are extended to infinity (original end dates are lost) - Future intervals and phases scheduled after the cancellation time are permanently lost - For complex subscriptions with phases or scheduled plan changes, consider creating a new plan change instead of uncancelling ## Subscription timeline Orb's subscription timeline allows you to visualize prices on a subscription over time along with the subscription's invoices, allowing you to easily understand the history of the subscription and preview any upcoming changes. Subscription timeline ### Understanding price visibility: Active vs. All prices When viewing subscription details, you have two options for price visibility that control which price intervals are displayed: * **Active prices**: Shows only the price intervals that are currently active (effective right now). This provides a snapshot of what prices are currently being charged on the subscription. * **All prices**: Shows the complete history and future schedule of price intervals on the subscription, including: * **Historical prices**: Previous price intervals that have ended * **Current prices**: Price intervals that are currently active * **Future prices**: Scheduled price intervals that will start in the future (e.g., planned price changes or add-ons) This enhanced visibility allows you to see the full pricing evolution of a subscription, including past pricing changes, current rates, and any scheduled future modifications. This is particularly useful for understanding the complete pricing history when managing subscription modifications, plan changes, or troubleshooting billing questions. The "All prices" view includes price intervals created through [subscription edits](/product-catalog/editing-subscriptions), plan changes, and any scheduled pricing modifications, giving you complete transparency into the subscription's pricing timeline. ## Trials A subscription's trial is determined by its trial configuration on the [plan](/core-concepts#plan-and-price) level. However, even in the case that a Subscription's plan is changed (see below), a subscription is always eligible for *at most one trial* and will never undergo a second trial, even if its new plan normally has a trial phase. ### Trial configuration Trials are configured at the plan level with two parameters: * **Duration in days**: The time period before charges begin * **Maximum usage discount**: The maximum dollar amount that will be discounted on the trial invoice Fixed fees are always excluded from trial charges. Usage-based charges accrue during the trial but are discounted up to the maximum discount amount. **Important limitations:** The maximum usage discount cannot be set to \$0. If you need a trial that tracks usage without applying any discount, use a 100% discount for the trial period instead. Trials end when the duration expires. They do not support flexible exit conditions like "30 days OR 1000 credits, whichever comes first." For trials with credit-based limits, use a separate trial plan with credit balance alerts to end the trial when credits are depleted. See [trials with prepaid credits](/self-serve/trial-management) for a complete implementation. ### Trial billing behavior During a trial, Orb generates invoices but applies the configured discount. If your trial includes a maximum usage discount, usage charges are tracked and discounted on the trial invoice. When the trial ends, normal billing begins. If you need trials that don't generate any invoices until a payment method is added, use a separate trial plan with no recurring charges. Grant credits outside the plan allocation so they persist when the customer upgrades to a paid plan. See [trials with prepaid credits](/self-serve/trial-management). ## Subscription price overrides When creating a customer's subscription to a plan where the existing plan terms need to be adjusted (e.g. because a specific amount has been negotiated differently), Orb provides the ability to *override* plan configuration. When creating a subscription, you may modify (override) or remove prices and adjustments from the plan, or add net new prices that did not exist on the plan. This allows the creation of a wide variety of custom plans using a small number of template plans, without having to construct the new plans from scratch. It also makes it easy to create add-ons to existing plans and subscriptions. Overrides to prices on a subscription will be applied by replacing the plan prices with newly created prices at the start of the subscription. Orb will replace the minimal amount of prices when creating the subscription, so any prices that have not been overridden will retain the same ids as the plan prices. Some existing Orb accounts may still be on Orb's legacy overrides behavior, which dynamically creates an entirely new plan to represent overrides rather than replacing the minimal set of prices and adjustments on the existing plan. If you're unsure which behavior your Orb account is using for overrides, please reach out to support. # Credit systems Source: https://docs.withorb.com/product-catalog/credit-systems Orb provides two distinct credit systems that serve different purposes in the billing lifecycle. Understanding when to use each is essential for accurate revenue recognition, customer experience, and financial reporting. ## The two systems at a glance | Aspect | Prepaid Credits (Credit Ledger) | Customer Balance | | ----------------------- | ----------------------------------------- | --------------------------------------------------- | | **Purpose** | Usage commitment and consumption tracking | Accounts receivable adjustment | | **Applied** | Before taxes, during invoice calculation | After taxes, at invoice issuance | | **Revenue recognition** | Yes, recognized as credits are consumed | No impact on revenue | | **Expiration** | Supports expiration dates | Never expires | | **Currency** | Real or virtual currencies | Customer's billing currency only | | **Audit trail** | Append-only ledger with sequence numbers | Transaction history with balance snapshots | | **Typical use** | Prepaid usage commitments, trial credits | Refunds, goodwill credits, small balance carryovers | ## Prepaid credits (Credit Ledger) The prepaid credit system is designed for customers who commit to usage upfront. Credits are organized into **blocks** with optional expiration dates and cost basis, stored in an **append-only ledger** that provides a complete audit trail. ### How prepaid credits work 1. **Credits are added** via direct API calls, subscription allocations, or automatic top-ups 2. **Blocks are created** with an amount, effective date, and optional expiration 3. **Usage accrues** throughout the billing period 4. **At invoice calculation**, credits are deducted from usage charges (in-arrears only) 5. **Remaining usage** after credit deduction becomes the amount due ### Key characteristics * **Scoped by currency**: Each customer can have multiple credit ledgers, one per pricing unit (e.g., USD, compute credits, storage credits) * **Block ordering**: Credits are deducted in a specific order—blocks with item filters first, then soonest-expiring, then by cost basis, then creation time * **In-arrears only**: Prepaid credits apply only to in-arrears charges (usage-based and fixed fees billed at period end) * **Pending vs committed**: Entries remain pending during the reporting grace period, then become immutable once committed ### When to use prepaid credits * **Usage commitments**: Customer prepays for a usage allocation (e.g., 10,000 API calls for \$500) * **Trial credits**: Provide credits that expire after a trial period * **Enterprise drawdown**: Large prepaid pools that draw down over months or years * **Multi-currency scenarios**: Track separate credit pools for different product SKUs ```json theme={null} // Example: Create a prepaid credit block POST /customers/{customer_id}/credits/ledger { "entry_type": "increment", "amount": 10000, "currency": "USD", "effective_date": "2024-01-01", "expiry_date": "2024-12-31", "per_unit_cost_basis": "0.05", "description": "Annual prepaid commitment" } ``` ## Customer balance Customer balance is a simpler system that acts like a digital wallet for accounts receivable adjustments. It represents credit or debit amounts that modify what the customer owes on their next invoice. ### How customer balance works 1. **Balance is modified** via manual adjustments, credit notes on paid invoices, or small balance carryovers 2. **At invoice issuance**, the balance is applied to reduce (or increase) the amount due 3. **The final amount due** reflects the balance adjustment after taxes ### Key characteristics * **Single currency**: Always in the customer's billing currency * **Post-tax application**: Applied after all line item calculations and taxes * **No revenue impact**: Does not affect revenue recognition—strictly an AR adjustment * **Immediate effect**: Applied to the next issued invoice automatically ### When to use customer balance * **Refunds via credit notes**: When a credit note is issued on a paid invoice, the refund amount goes to customer balance * **Goodwill credits**: One-time credits for customer satisfaction issues * **Corrections**: Manual adjustments to reconcile billing discrepancies * **Small balance carryovers**: Amounts below payment minimums are carried forward ```json theme={null} // Example: Add a goodwill credit to customer balance POST /customers/{customer_id}/balance_transactions { "type": "increment", "amount": "50.00", "description": "Goodwill credit for service interruption" } ``` ## Calculation order on an invoice Understanding where each credit system applies in the invoice calculation pipeline is critical: ```text theme={null} 1. Quantity calculation → Billable metric evaluation 2. Subtotal → Quantity × rate 3. Adjustments → Discounts, minimums, maximums 4. Prepaid credits → Deducted from adjusted subtotal ← PREPAID CREDITS 5. Currency conversion → Virtual to real currency 6. Previously invoiced → Subtract threshold billing amounts 7. Tax → Tax provider calculation 8. Customer balance → Applied to final amount due ← CUSTOMER BALANCE ``` ## Common scenarios ### Scenario 1: Customer with both systems A customer has: * \$500 in prepaid credits (Credit Ledger) * \$100 in customer balance (from a previous refund) Invoice calculation: ```text theme={null} Usage charges: $800 - Prepaid credits: $500 = Subtotal before tax: $300 + Tax (10%): $30 = Total with tax: $330 - Customer balance: $100 = Amount due: $230 ``` ### Scenario 2: Credits don't cover the minimum A customer has a \$300 minimum on their plan and \$200 in prepaid credits. Invoice calculation: ```text theme={null} Usage charges: $150 Apply minimum: $300 (minimum kicks in) - Prepaid credits: $200 = Amount due: $100 ``` Note: The minimum is applied *before* prepaid credits. This prevents customers from using credits to avoid contractual minimums, while still allowing the credits to reduce the total amount due. ### Scenario 3: Prepaid credits with virtual currency A customer has 1,000 compute credits (virtual currency) with a \$0.50 conversion rate. Invoice calculation: ```text theme={null} Compute usage: 800 credits - Prepaid credits: 800 credits (deducted) = Overage: 0 credits = Converted to USD: $0 // If overage existed: Compute usage: 1,200 credits - Prepaid credits: 1,000 credits = Overage: 200 credits = Converted to USD: $100 (200 × $0.50) ``` ## Migrating between systems ### Moving from customer balance to prepaid credits If you've been using customer balance for usage commitments, consider migrating to prepaid credits for: * Better revenue recognition tracking * Expiration date support * Multiple currency/pricing unit support * Detailed consumption audit trail ### Moving from prepaid credits to customer balance Customer balance is simpler but loses: * Expiration dates (credits never expire) * Cost basis tracking (no revenue recognition) * Block-level granularity (just a running total) ## Best practices 1. **Use prepaid credits for usage commitments**: When customers prepay for usage, use the credit ledger for proper revenue recognition and expiration handling. 2. **Use customer balance for AR adjustments**: Refunds, corrections, and goodwill credits that don't represent usage commitments belong in customer balance. 3. **Don't mix purposes**: Avoid using customer balance as a workaround for prepaid credits, as this will cause revenue recognition issues. 4. **Monitor credit block expiration**: Set up balance alerts to notify customers before their prepaid credits expire. 5. **Consider cost basis**: When creating prepaid credit blocks, set the cost basis for accurate revenue reporting—trial credits typically have \$0 cost basis. # Customer hierarchy Source: https://docs.withorb.com/product-catalog/customer-hierarchy Orb has support for creating hierarchical relationships between your customers to power flexible pricing, usage aggregation, and invoicing. This capability is particularly valuable when your customers desire to aggregate usage across multiple customers on a single parent invoice while maintaining the ability to configure pricing per customer or groups of customers. Customer hierarchy is not included with all Orb billing plans by default. Reach out to your Orb representative if you have any questions. Customer hierarchy works in two parts: 1. Create a hierarchy relationship between a parent and child customer(s) 2. Configure pricing to use the hierarchy relationship ### Create a hierarchy relationship To get started, ensure your plan includes this feature. Browse to the [settings page](https://app.withorb.com/settings) and enable the "Customer hierarchy" feature. Once Customer hierarchy is enabled, it cannot be disabled. Enable customer hierarchy A hierarchy relationship can be created when creating a new customer or editing an existing customer. A parent customer can have no more than 100 child customers. #### Creating a customer as a parent In the Orb UI, navigate to the [customer creation page](https://app.withorb.com/customers/new). Specify the child customers to include in the hierarchy for the newly created parent customer. Create the child customers first before creating the parent customer, or create the parent customer first and then add the hierarchy relationships with child customers later. Customers with existing hierarchy relationships cannot be added to another hierarchy. Customer hierarchy create parent customer In the Orb API, a hierarchy relationship can be created when [creating a customer](https://docs.withorb.com/api-reference/customer/create-customer#body-hierarchy) by setting the `hierarchy` field and specifying the child customers to include in the hierarchy. ```json theme={null} { "name": "Acme Corp", "email": "billing@acme.dev", "hierarchy": { "child_customer_ids": ["child_customer_id_1", "child_customer_id_2"] } } ``` #### Creating a customer as a child In the Orb UI, navigate to the [customer creation page](https://app.withorb.com/customers/new). Specify the parent customer to include in the hierarchy for the newly created child customer. Customer hierarchy create child customer In the Orb API, a hierarchy relationship can be created when [creating a customer](https://docs.withorb.com/api-reference/customer/create-customer#body-hierarchy) by setting the `hierarchy` field and specifying the parent customer to include in the hierarchy. ```json theme={null} { "name": "Acme Legal", "email": "legal@acme.dev", "hierarchy": { "parent_customer_id": "parent_customer_id" } } ``` #### Update a customer to be a child or parent In the Orb UI, navigate to the [customers page](https://app.withorb.com/customers) and select the customer you want to update. Scroll to the `Customer details` section to edit the customer. Customer hierarchy edit child customer In the Orb API, a hierarchy relationship can be created when [updating a customer](https://docs.withorb.com/api-reference/customer/update-customer#body-hierarchy) by setting the `hierarchy` field and specifying either the parent customer or child customer(s) to include in the hierarchy. ### Configure pricing to use a hierarchy relationship With Customer hierarchy, you can now aggregate the usage of multiple customers on a single parent invoice. This enables the following billing configurations: 1. Bill any mix of parent and child customers 2. Bill child customers only 3. Bill groups of child customers for different prices, with the same or different billable metric With an existing hierarchy relationship, you can configure pricing to use the hierarchy relationship when creating a subscription or modifying the subscription price intervals. #### Bill parent and child customers In the Orb UI, create a new subscription and specify the parent and child customers to bill. Customer hierarchy subscription create In the Orb API, [create a new subscription](https://docs.withorb.com/api-reference/subscription/create-subscription#body-usage-customer-ids) and specify the parent and child customers to bill. ```json theme={null} { "plan_id": "plan_id", "customer_id": "parent_customer_id", "start_date": "2025-01-01", "usage_customer_ids": ["parent_customer_id", "child_customer_id_1", "child_customer_id_2"] } ``` #### Bill child customers only In the Orb UI, create a new subscription and specify only the child customers to bill. In the Orb API, [create a new subscription](https://docs.withorb.com/api-reference/subscription/create-subscription#body-usage-customer-ids) and specify only the child customers to bill. ```json theme={null} { "plan_id": "plan_id", "customer_id": "parent_customer_id", "start_date": "2025-01-01", "usage_customer_ids": ["child_customer_id_1", "child_customer_id_2"] } ``` #### Specify customers to bill per price A powerful feature of the hierarchy relationship is the ability to specify which customers will be billed individually or as part of a group for a given price. Toggle the `Configure customers per price` option to select specific customers from the hierarchy relationship for each price. Customer hierarchy subscription create configure customers Prices can also be duplicated to apply a different rate to a different group of customers for the same billable metric. Duplicating a billable metric could lead to double billing of usage if the same customer is included in different price intervals with the same billable metric. Below, a tiered price is used to rate the data storage usage for a group of customers in the hierarchy. That price is duplicated and modified to instead use a per-unit rate for another group of customers. Customer hierarchy subscription create price rates duplicate In the Orb API, a existing price interval on a subscription can be [modified](https://docs.withorb.com/api-reference/price-interval/add-or-edit-price-intervals#body-edit), or a new price interval can be [added](https://docs.withorb.com/api-reference/price-interval/add-or-edit-price-intervals#body-add), with the `usage_customer_ids` property set to specify the customers to bill. ```json theme={null} { "add": [ { "start_date": "2025-01-01", "price_id": "price_id", "usage_customer_ids": [ "child_customer_id_1", "child_customer_id_2" ] } ], "edit": [ { "price_interval_id": "price_interval_id", "usage_customer_ids": [ "parent_customer_id", "child_customer_id_2" ] } ] } ``` ### Modify a hierarchy relationship A hierarchy relationship that is in use in an active or upcoming subscription price cannot be modified. Cancel or end the subscription price before modifying the hierarchy relationship. A hierarchy relationship can be modified by editing the customer. Note that a hierarchy relationship can only be modified if it is not in use in an active or upcoming subscription. Customer hierarchy edit active subscriptions ## Invoices Customer hierarchy leads to usage aggregation at the parent customer level. This means that the usage for a subscription using a hierarchy relationship will be invoiced to the parent customer. In the Orb UI, on the customer details page for the parent customer, you can view the customer hierarchy table and the invoicing mode for each child customer. Customer hierarchy invoicing table Additionally, line items on an invoice will denote the billed customers Customer hierarchy invoice view In the Orb API, the `usage_customer_ids` [property](https://docs.withorb.com/api-reference/invoice/fetch-invoice#response-line-items-usage-customer-ids) on an invoice line item will denote the billed customers. ### Customer evaluation operations There are several evaluation endpoints provided in the Orb API to build visualizations atop your event data and metrics. These operations can be used to evaluate prices, costs, and usage for a given customer or subscription. Here is a table summarizing the result of the evaluation operations based on the provided customer type in the hierarchy relationship. | Operation | Parent Customer | Child Customer | | :------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------- | :--------------------------------------------------------- | | [Evaluate price](https://docs.withorb.com/api-reference/price/evaluate-price) | This will evaluate the price solely for the parent customer | This will evaluate the price solely for the child customer | | [Customer costs](https://docs.withorb.com/api-reference/customer/fetch-customer-costs) | This will aggregate costs across the hierarchy relationship in each subscription | Nothing will be returned for the child customer | | [Subscription costs](https://docs.withorb.com/api-reference/subscription/fetch-subscription-costs) | This will aggregate costs across the hierarchy relationship | Nothing will be returned for the child customer | | [Subscription usage](https://docs.withorb.com/api-reference/subscription/fetch-subscription-usage) | This will aggregate usage across the hierarchy relationship | Nothing will be returned for the child customer | ### Frequently Asked Questions #### Can I create a hierarchy relationship between a parent and child in different currencies? A hierarchy relationship can be created between a parent and child customers with different currencies. However since usage will be aggregated across the hierarchy and invoiced to the parent, only the currency of the parent will be used for the invoice. #### Can I create a hierarchy relationship between a parent and child with different tax applications? Similar to the differing currencies, since usage will be aggregated across the hierarchy and invoiced to the parent customer, only the tax application of the parent customer will be used for the invoice. #### Will a newly created child customer inherit the subscription pricing from the parent customer? The child customer will not inherit any existing subscription pricing from the parent customer. The prices on the parent customer's subscription will need to be modified to include usage of the child customer. This can be done via the [price intervals add or edit API](https://docs.withorb.com/api-reference/price-interval/add-or-edit-price-intervals) #### Does this feature support resellers and MSPs? Customer hierarchy is not currently designed to support resellers and MSPs. For implementation guidance or support, reach out to your Orb representative. #### How does this feature interact with deductions from a customer's credit ledger? The [ledger](https://docs.withorb.com/product-catalog/prepurchase#prepaid-ledger) is scoped to a specific customer. Since invoicing for a subscription using a hierarchy relationship happens at the parent customer level, any usage (parent or child customers) will be deducted from the parent customer's ledger. The ledger will show a single entry for usage deduction instead of a disaggregated breakdown per child customer. In other words, usage on a subscription linked solely to a child customer will drawdown only from the child customer's ledger. However child customer usage on a parent customer subscription using a hierarchy relationship will drawdown from the parent customer's ledger. **Planning credit purchases for hierarchies**: Purchase credits at the parent customer level when you want them consumed by hierarchy subscriptions. Credits granted directly to child customers can only be used by that child's individual subscriptions (if any), not by hierarchy subscriptions on the parent. #### How does this feature interact with the subscription lifecycle? ##### Plan change A [subscription plan change](https://docs.withorb.com/product-catalog/modifying-subscriptions#subscription-plan-changes) ends the current price intervals on the subscription. As a result, for a subscription using a hierarchy relationship, any price interval usage customers configuration will be lost and needs to be re-configured. ##### Plan version and migration Performing a [subscription migration](https://docs.withorb.com/product-catalog/price-changes#migrating-subscriptions-to-a-new-version) does not affect the hierarchy relationship on existing price intervals. New price intervals created as part of a migration will bill for usage of all customers in the hierarchy relationship. #### How does this feature interact with manually triggered phases? Customer hierarchy is not supported when creating a subscription with manually triggered phases. Manually triggered phases lead to the creation of new price intervals at the point of the trigger which can then be edited via `usage_customer_ids` property in the [price intervals add or edit API](https://docs.withorb.com/api-reference/price-interval/add-or-edit-price-intervals) to specify the customers to bill. # Dimensional pricing Source: https://docs.withorb.com/product-catalog/dimensional-pricing Dimensional pricing allows you to create sophisticated pricing models that vary based on multiple dimensions or properties of your usage events. It offers **unlimited flexibility** to price each dimension independently, making it ideal for complex, large-scale billing use cases. ***Dimensional pricing replaces the deprecated Matrix pricing model, which was limited to two dimensions. If you’re migrating from Matrix pricing, see this ***[***guide***](/product-catalog/dimensional-pricing#migration-from-matrix-pricing)***.*** ## What is dimensional pricing? Dimensional pricing groups your usage data by one or more dimensions (properties), then applies pricing rules to each unique combination of dimension values. Think of it as applying a "GROUP BY" operation across your chosen dimensions, then pricing each group separately. For example, you might want to price compute usage differently based on the unique combinations of: * **Region** (us-east-1 vs eu-west-1) * **Instance type** (small vs large vs xlarge) * **Environment** (production vs staging) With dimensional pricing, you can create a single price group that handles all these combinations automatically. ## Key benefits * **Unlimited dimensions**: Define pricing across any number of usage dimensions - such as region, environment, or instance type - for granular control and flexibility. * **Better performance**: Optimized to handle complex, large-scale pricing scenarios efficiently. * **Simplified management**: One pricing configuration covers all combinations of dimensions, reducing setup and maintenance overhead. * **Flexible discounts**: Apply discounts across specific dimensions or dimension combinations ## How it works ### 1. Define your dimensions First, identify which event properties you want to use as pricing dimensions. These should be properties that are consistently present in your events and represent meaningful pricing differentiators. Common dimension examples: * `region` - Geographic location * `instance_type` - Resource size or tier * `environment` - Production vs staging * `product_tier` - Different product offerings * `data_center` - Physical infrastructure location ### 2. Create a dimensional price group Create a dimensional price group that defines the dimensions and their possible values with pricing for each combination. ### 3. Configure prices to use the group Create prices that reference the dimensional price group and specify which dimension values should be used for that specific price. Here's how to configure dimensional pricing via the API: ```json theme={null} { "model_type": "unit", "unit_config": { "unit_amount": "1.00" }, "dimensional_price_group_config": { "external_dimensional_price_group_id": "region-instance-pricing", "dimension_values": ["us-east-1", "small"] } } ``` In this example: * The price uses any pricing model (`unit` in this case) * It references an existing dimensional price group by `external_dimensional_price_group_id` * The `dimension_values` array specifies the exact dimension combination this price applies to, in the same order as defined in the group You can also reference a group by its internal ID: ```json theme={null} { "model_type": "tiered", "tiered_config": { "tiers": [ { "first_unit": 0, "last_unit": 100, "unit_amount": "0.10" } ] }, "dimensional_price_group_config": { "dimensional_price_group_id": "dpg_12345", "dimension_values": ["eu-west-1", "large"] } } ``` ## Setting up dimensional pricing ### In the Orb dashboard 1. **Create a dimensional price group** during plan creation or subscription creation by specifying the dimensions you want to use Create dimensional price group 2. **Add a price to the dimensional price group** (just like adding a price to a plan) Add price to dimensional price group 3. **Specify the price's dimension values** for the specific combination this price applies to Specify dimension values 4. **Choose any pricing model** (unit, tiered, etc.) and configure it as usual ### Via API 1. [**Create a dimensional price group**](/api-reference/dimensional-price-group/create-dimensional-price-group) first 2. [**Use the Create Price API endpoint**](/api-reference/price/create-price) with the `dimensional_price_group_config` object as shown in the configuration example above ## Working with dimensional price groups Dimensional pricing creates **Dimensional Price Groups** that manage the relationships between dimensions and their pricing. Each dimensional price group: * Has an optional `external_id` that can be used to reference the group in API calls * Contains all dimension value combinations and their pricing * Can be reused across multiple prices and plans * Supports updates to pricing without affecting active subscriptions ### Managing dimensional price groups You can: * [**List all dimensional price groups**](/api-reference/dimensional-price-group/list-dimensional-price-groups) in your account * [**Create a new dimensional price group**](/api-reference/dimensional-price-group/create-dimensional-price-group) ## Best practices ### 1. Plan your dimensions carefully * Choose dimensions that are stable and consistently present in your events * Avoid using dimensions with high cardinality (too many unique values) * Consider your pricing strategy before implementing ### 2. Use meaningful external IDs * Give your dimensional price groups descriptive `external_id` values when you need to reference them programmatically * This makes them easier to manage and reference in API calls * Example: "compute-region-instance-pricing" vs "dpg-123" ### 3. Configure all necessary combinations * Explicitly define pricing for all dimension combinations you expect to encounter * Monitor for new dimension values in your usage data * Add new combinations as your product evolves ### 4. Test thoroughly * Build these in test mode first, and also ensure your code has been updated to reference the new dimensional pricing before migrating to dimensional pricing in production * Verify pricing works correctly for all dimension combinations * Use Orb's [simulation tools](/simulations/introduction) to test complex scenarios * Check invoice calculations before going live ## Troubleshooting ### Common issues **Missing dimension values in events** * Ensure all required dimensions are present in your usage events * Verify dimension values in your events exactly match the configured values **Pricing not applied correctly** * Check for case sensitivity and formatting differences * Ensure all expected dimension combinations are explicitly configured * Verify the `dimension_values` array matches the order defined in the price group ### Getting help If you need assistance with dimensional pricing: * Check the [API reference](/api-reference) for detailed endpoint documentation * Review the [quickstart pricing guide](/quickstart/pricing) for implementation examples * See the [data exports guide](/data-exports/resource-types#dimensional-price-group) for dimensional price group schema information * Learn about [other pricing models](/product-catalog/build-catalog) available in Orb * Contact [support](https://portal.usepylon.com/orb/forms/orb-support) for complex pricing scenario guidance ## Migration from matrix pricing Migration from matrix pricing to dimensional pricing can be done automatically for a specific plan as part of creating a new plan version. We recommend that you run this first in your test mode before running in production, and also making sure your code is updated to reference the dimensional pricing. Matrix to dimensional pricing migration ### Automatic migration When you create a new plan version, Orb will provide you with the ability to automatically preview and convert eligible matrix prices into dimensional pricing. This includes carrying over relevant billable metrics, pricing values, and dimension keys into a corresponding dimensional price group. **Current limitations of dimensional price groups:** * We can't automatically convert usage discounts * There's currently no equivalent support in dimensional pricing for the "default unit amount" on a matrix price The migration process is designed to be conservative: it automatically converts prices where it can do so safely and **intentionally fails** in cases that may introduce ambiguity, so you have full control over the outcome. **Individual subscription migration failures:** Orb’s migration flow is intentionally conservative when it comes to migrating individual subscriptions. In scenarios where automatic conversion could lead to unintended billing outcomes, we’ll intentionally fail those subscriptions so you can review them manually. We will fail to automatically migrate individual subscriptions where: 1. The subscription includes **overrides** for the matrix price - these may reflect intentional customizations that should be reviewed 2. The matrix price has already ended at the effective time of the migration - meaning it’s no longer active, and Orb can’t safely determine a 1:1 replacement ### Handling failed migrations For individual subscriptions that have failed to migrate automatically: * **Plan change approach**: A plan change to the same plan at the new version will effectively act as a version upgrade if scheduled at a billing period boundary, and will often have exactly the behavior you need * **Need support**: If there are too many failed subscriptions, or a plan change won't have the behavior you're expecting, [reach out to Orb support for assistance](https://portal.usepylon.com/orb/forms/orb-support) The key advantage of migrating to dimensional pricing is unlimited dimensions support with better performance and easier management. # Subscription edits Source: https://docs.withorb.com/product-catalog/editing-subscriptions Subscription Edits in the UI is early access - if you can’t find the flow, ask your Orb admin or Customer Success Manager to enable early access for your account. Subscription Edits let you change a single subscription’s pricing configuration directly from Orb’s web interface or via the API - add or remove add-ons, update fixed-fee quantities (seats), apply or edit discounts, maximums, minimums, and schedule effective dates - **without changing the plan itself**. Edits create new price intervals on the subscription and preserve a full historical trail so billing remains predictable and auditable. ## Why use subscription edits? * Make **customer-specific changes** (rates, time-bound discounts, billable metric changes, or special seat counts) without editing your catalog. * **Schedule** price or quantity changes to take effect immediately or at a future billing cycle. * **Preview** the billing and invoice impact before you save, so you can confirm how charges and invoices will look. ## Before you start 1. You must have editor permissions for subscriptions in Orb. 2. Recommended: open the subscription from the subscription details page so you have context on history and invoices. ## Open the editor 1. Go to the subscription you want to edit. 2. In the header, open the action menu (vertical dots) and choose **Edit subscription**. Image Pn *If you don’t see the action, you may not have the right permission or the feature may not be enabled for your org.* ## Editor layout (what you’ll see) The Subscription Editor uses a two-column layout: * **Left column (Main)** - subscription summary, subscription dates, and **Pricing configuration**. This is where you add/remove prices (add-ons), update fixed-fee quantities, edit discounts/adjustments, and manage allocations. * **Right column (Preview)** - timeline and invoice preview that updates as you make draft changes so you can inspect the billing impact before saving. Editpage Pn The editor validates interval dates, prevents invalid overlaps, and blocks edits that would affect trial prices. *** ## Step-by-step: common flows ### Add a new price to a subscription You can add individual prices to Orb subscriptions at any time. This can be used to easily model feature add-ons, where customers can opt in or out of paying for additional features, or to add new SKUs to subscriptions as your pricing evolves. You can also add a price to a subscription using the [subscription price intervals API](/api-reference/price-interval/add-or-edit-price-intervals). For example, imagine that you're a web hosting provider billing your customers monthly, and you want to allow users to start using a new optional storage feature and bill them for their usage. You can add a new price called "Storage (Gb)" to the subscription starting at the beginning of the month. This will charge the customer on their end of month invoice for any usage of the storage feature in the current month - removing the need to wait for the end of a billing cycle to start billing for a new service. You can also add prices starting in the future, allowing you to stage price changes in advance. For example, this allows you to add prices starting at the beginning of the next billing cycle and preview how they'll look on invoices before customers see any changes to their subscription or pricing. Addprice Pn 1. In **Pricing configuration**, click **Add** (or the add-price button). 2. Complete the add-on details (name, whether it’s usage-based or a fixed fee, cadence). 3. Choose the interval dates (start and optional end) or select an effective date relative to the billing cycle. 4. Use the Preview to confirm invoices and expected charges, then **Save**. Addusageprice Pn **When to use:** Add a new price when you need to change **what a single customer pays** without touching the shared plan - for example a one-off add-on, a usage meter, or a negotiated/promotional charge. It’s ideal for grandfathering key customers, reserving credits or seat bundles, or quickly testing monetization while preserving plan history. ### One-time discount or minimum Adjustments (discounts, minimums, and maximums) on a subscription can be added, removed, and changed in exactly the same way as prices using the [subscription price intervals API](/api-reference/price-interval/add-or-edit-price-intervals) or in the UI. This allows you to flexibly update a subscription's pricing to enable use cases like creating temporary discounts or applying discounts to add-on prices on a subscription. One-off adjustments let you change a single invoice or a short invoice period for a subscription without changing the plan. A **one-time discount** reduces an invoice line (percentage or fixed amount). A **minimum** guarantees an invoice won’t fall below a set revenue floor. Priceoptions Pn 1. In **Pricing configuration**, click **Add → Discount** or **Add → Adjustment → Minimum**. 2. Configure the adjustment: * **One-time discount:** choose **Percentage** or **Amount**, and enter the value. * **Minimum:** enter the minimum amount and (optional) item id for revenue attribution. * Choose the **scope**: single price, all prices, or a filter. (Price-level adjustments must target exactly one price.) 3. Set the **interval / effective dates** so the adjustment lines up with the invoice you want to affect - for a single invoice set start = billing boundary and end = next billing boundary. 4. Preview the timeline and invoice diff on the right, confirm proration and scope, then **Save**. Minimum Pn **When to use:** correct a billing error, grant a one-off concession, run a single-period promotion, or enforce a contractual minimum for a billing period. **Scheduling - pick the right date** * **Prefer start-of-term.** Schedule adjustments to begin at the next billing boundary to avoid proration and to make it clear which invoice is affected. * **Mid-cycle starts cause proration.** If an adjustment begins mid-cycle it will often be prorated or may only apply to the next invoice depending on coverage rules. Preview to confirm behavior. * **Target a single invoice.** To affect exactly one upcoming invoice, set the interval to match that invoice period (start = billing boundary, end = next billing boundary). **Quick tips & gotchas** * Choose **invoice-level** to affect the whole invoice; choose **price-level** to affect only a specific price line. * You cannot add an adjustment that starts inside an active trial period. * The editor blocks overlapping adjustments for the same price/invoice window — check validation errors if your change is rejected. * Use the Preview panel every time - it shows exactly which invoice(s) will change and any prorated amounts. ### Replace or edit a price on a subscription Editusageprice Pn 1. In **Pricing configuration**, locate the price (or billable metric / product) you want to retire or edit. 2. Select **Edit** on the existing price, change the fields you need (rate, metric, item, quantity schedule), and when prompted select the **effective / replacement date** for the new interval. 3. Prefer start-of-term effective dates to avoid proration; if you must use a mid-cycle date, review the prorated amounts in Preview. 4. Use the right-hand **Preview** to confirm the old interval ends and the new interval starts where you expect, then **Save**. Editing creates a new price starting on your selected effective date. Adjustments filtered to the original price may not apply to the new price by default. Add the new price to the adjustment if needed. Editusageprice Dates Pn **When to use:** Replace a price when you need a subscription to move from one price/metric/product to another at a specific future date — use **Edit + schedule replacement** for quick replacements and **Cancel + Add** when you want explicit end + create control. ### Change a fixed-fee quantity You can also change the quantity to charge for a fixed fee at any time, past or future. For example, imagine that you charge a fixed fee for seats in addition to usage. If a customer wants to increase their number of seats, you can also use the [Orb API](/api-reference/price-interval/add-or-edit-price-intervals#add-or-edit-price-intervals) to initiate an immediate change to the price's quantity. This will generate an invoice for the price of the additional seats, prorated to the remaining duration of the billing period. FFQS Pn 1. Find the fixed-fee price (for seats) in Pricing configuration. 2. Use **Edit quantity schedule** or open the price and change the quantity (you can schedule it now or for the start of a future billing cycle). 3. Preview the invoice to confirm the prorated charge (if changing mid-billing period) or the next-billing-cycle behavior. 4. Save when satisfied. **Note:** Immediate quantity increases are commonly prorated to the remainder of the billing cycle; scheduling at cycle start avoids proration. ### Remove or undo a change * Use **Remove** on a price card to schedule an interval to end. * Use **Undo** (Reset) to revert a draft change and restore the original interval. * Use **Duplicate** to clone an interval if you want to experiment without changing the original. ## Previewing your changes The right-hand panel shows a subscription **timeline** and an **invoice diff preview**. The preview is created from the draft changes you’re editing and computes expected invoices (including usage where applicable) so you can confirm the effect of edits before saving. **Preview notes** * The preview covers a timespan based on your subscription start date and extends a few months forward by default. * If the editor detects a top-level validation problem (for example overlapping intervals or a trial-period violation), the preview will be disabled until you resolve the issue. * The preview helps catch unwanted spikes, missing invoices, or proration surprises before saving. ## Confirming subscription edit changes and saving When you click **Save,** the editor gives a summary of the pricing changes made, as well as the expected number of invoices or credits that will be issued upon saving. Previewandsave Pn Orb applies the new intervals, generates invoices and credits as needed, and preserves the subscription’s timeline so invoices and history remain auditable. After a successful save you are returned to the subscription detail page, where you can see the subscription edit logged in your timeline. ## Customer FAQ **Will editing a subscription change the plan?** No. Subscription Edits are subscription-scoped: they create or replace intervals only on that subscription. The underlying plan in your catalog is unchanged. **Can I edit a trial price?** No. Trial prices are protected; you cannot edit trial prices or add prices that start inside an active trial period. **What happens when I increase seat counts mid-cycle?** Increases are typically prorated for the remainder of the billing cycle. If you want to avoid proration, schedule the change to start at the next billing-cycle boundary. **Do add-ons follow a plan change?** No. Add-ons applied directly to a subscription remain subscription-scoped unless you explicitly remove or change them. ## Need help? If you run into unexpected results in the preview or after saving, please contact Orb Support and include: * Subscription ID * A short description of the change you attempted * Screenshots of the editor and preview # License alerting Source: https://docs.withorb.com/product-catalog/license-alerting # Introduction License scoped alerts extend Orb’s [real‑time alerting](https://docs.withorb.com/usage-alerting/overview) so you can monitor and act on usage and credit consumption at the level of an individual license (for example, a specific user, agent, or workspace), not just at the customer or shared‑pool level. With license scoped alerts, Orb continuously tracks per‑license usage and remaining allocation availability. When a license’s pool crosses a configured threshold—such as 75%, 90%, or 100% consumed—Orb does the following: * Render in‑app notifications in the Orb UI * Fire webhooks that include license identifiers and balance details * Feed downstream systems (entitlements, customer messaging, internal tooling) so you can automatically adjust access, trigger upsell flows, or notify operators for follow‑up This gives you fine‑grained control and optionality to: * Seamlessly run freemium / trial programs, without the COGS risk * Enforce product access or restrictions, like gating premium features * Control spend and enforce fairness # Operator’s guide 1. When configuring a plan or subscription, add a license allocation alert. 1. Alerts configured at the plan-level will be inherited by any subscriptions created for that plan. 2. Orb recommends creating the following alerts at the plan level: 1. **License allocation depleted** – usage consumes 100% of available allocation for the billing period, product access should be restricted. 2. **License allocation replenished** – usage resets to 0% of available allocation for the billing period, product access should be restored. 3. **License allocation exceeds** – usage has crossed some X% threshold of available allocation for the billing period, which can be used as a soft-limit to prevent abuse and overage, or as a trigger for upsell. 3. Configured alerts will fire an (aggregated) webhook that can be used to hook into downstream systems and workflows (entitlements, customer messaging, internal tooling). Licenses7 # End-to-end entitlements management ## Sample workflow 1. Create a license type, a plan with licenses and per-license allocations, and configure license allocation alerts for key thresholds. (See Operator’s Guide above). 2. Create a subscription and activate licenses. 1. License identifiers are typically sourced from your existing identity management system. 2. Many teams reuse a stable ID from their identity provider (Ex: Okta, Azure, AD) such as `user_id` or `external_license_id`. 3. As license usage is ingested, when the license allocation alert 1. **Is depleted**, turn off product access by leveraging the following fields: 1. `alert_configuration.threshold` – 0% 2. `fired_licenses[]` – the specific licenses that just crossed a threshold. 1. `external_license_id`: your user / seat / agent identifier to disable. 2. `license_type_id`: which license type this applies to 2. **Is replenished**, turn on product access by leveraging the following fields: 1. `reset_allocations[]` – the specific licenses need to be reset. 3. **Exceeds custom threshold**, send upsell banners, in-product notifications by leveraging the following fields: 1. `alert_configuration.thresholds` – all configured thresholds (for example, 25%, 50%, 100%). 2. `fired_licenses[]` – the specific licenses that just crossed a threshold: 1. `external_license_id`: your user / seat / agent identifier 2. `license_type_id`: which license type this applies to 3. `threshold_percentage`: which threshold was hit (for example, "`50`" or "`100`") # License allocations Source: https://docs.withorb.com/product-catalog/license-allocations # Introduction Licenses allow you to model generic entitlements in Orb, where usage can be attributed to a unique license for a billing period. The most common application of licenses is to model seat-based pricing in Orb, like for Anthropic Claude or OpenAI Codex, but can be extended to represent anything from bots, agents, workflows, machines. etc Each license can include its own allocation of credits to handle proper billing, fair-usage enforcement, reporting, while still participating in the shared subscription credit pool and standard account-level overage billing. With license allocations, you can: * Track and bill for usage per team member * Provide a more seamless upsell and upgrade experience * Run detailed COGS analyses to understand unit economics by user * Prevent contamination of the shared team-wide allocation pool # Key components ## License type A license type is a named pricing component that tells Orb how to attribute usage events to a licensed entity (such as a seat, bot, agent, workflow, machine, etc.). Each license type is defined by a license grouping key – this is a unique event property that identifies which license a usage event belongs to. Example: If your plans have multiple seat types (Pro, Developer, or Free), create one license type per seat type in Orb. Usage events with a key value pair of `pro_seat` = `john_doe` will attribute those events to John’s Pro license. License types are configured in [Settings](https://app.withorb.com/settings?tab=licenses), and can be globally used across all plans and subscriptions. Licenses1 ## License price A license price is a fixed-fee price that represents the cost of a single entitlement on a subscription. It is explicitly tied to a license type. It defines how much to charge per license per billing period, and the quantity of licenses to provision. Add a license price when [creating a new plan](https://docs.withorb.com/product-catalog/build-catalog) to provision licenses on subscriptions. Currently, you can have one license price per plan (and per subscription). License prices can only be configured on plans with fixed fees and unit-based usage prices. Licenses3 [Custom invoice grouping](https://docs.withorb.com/invoicing/structure#custom-invoice-grouping)**s** of usage line items is not available for plans with a license price at this time. ## Usage To attribute usage to a specific license, your usage events must include both the **license grouping key** defined by the license type, and the **license value** for the individual license as a defined property of the event. Orb uses this key value pair to determine which license a usage event should draw down from. Usage also has to be rated on a price that is eligible for the license’s allocation, as described in [Eligible prices](#eligible-prices). Example: If your license type references `user_id`, a usage event for user *John Doe* should include: * **license grouping key:** `user_id` * **license value:** `john_doe` Sample payload: ``` { "idempotency_key": "", "event_name": "usage_event", "external_customer_id": "", "customer_id": "", "timestamp": "2026-01-01T00:00:00.000000Z", "properties": { "user_id": "john_doe" } } ``` Orb will then attribute that usage to the license associated with `user_id` = `john_doe`, upon license activation. For help [getting started with event ingestion](https://docs.withorb.com/events-and-metrics/event-ingestion#events-and-metrics), check out our docs. ## License allocation When configuring a license price, each license can optionally include its own allocation of credits to handle proper billing, fair-usage enforcement and reporting. “Add allocation” and define the credit currency and amount that should be provisioned with each license. License allocations are only applied to usage associated with a license, after it has been activated. Licenses10 ## Eligible prices A license allocation only draws down against usage prices that are eligible for it. Eligibility is set per price. A usage price that is not included bills normally, drawing down the shared credit pool or billing as overage, even when its events carry the license grouping key. A usage price is eligible only if it: * uses the **unit** pricing model. Other pricing models cannot draw down from a license allocation * is in the same currency as the license allocation In the plan editor, the license price has a **Prices eligible to draw down from allocation** field with two options. **All eligible prices** includes every unit-based usage price in the allocation’s currency that is on the plan at the time you save. **Selected prices** includes only the prices you pick. **All eligible prices** is applied when you save, and is not re-evaluated afterwards. A usage price added in a later plan version is not included automatically, and the field then reads **Selected prices**, listing the prices that were included earlier. When you add a usage price to a plan that has a license allocation, open the license price and check the eligible price list before publishing. ### Eligible prices in the API In the API, eligibility is set on each price individually with `license_type_id`. There is no equivalent of **All eligible prices**, so the field belongs on every usage price you want covered, in every request that creates one, including `POST /plans/{plan_id}/versions`: ```json theme={null} { "version": 3, "add_prices": [ { "price": { "name": "Input tokens, model X", "item_id": "", "billable_metric_id": "", "model_type": "unit", "currency": "USD", "unit_config": { "unit_amount": "0.02" }, "license_type_id": "" } } ] } ``` `license_type_id` is optional, so a price that omits it is accepted and created as an ordinary usage price. The license price and its allocation are created with `license_allocation_price`. An `allocation_price` grants standard credits and does not create a license allocation, even when a `license_type_id` is set on it. On an invoice, a line item covered by a license allocation shows a **license allocation applied** amount. # License lifecycle management ## License management When a customer subscribes to a plan that includes licenses, the subscription automatically gains a Licenses tab. This tab is your command center for managing licenses. You can activate, deactivate, and track licenses across the subscription. It also serves as a reporting surface, showing all provisioned licenses and their current status in one place. Licenses4 ## License activation Activating a license in Orb is the mechanism by which usage will start being attributed to that license, given a unique license grouping key to register that license. Actual activation time is date aligned – any usage events ingested will be attributed to that license starting at 00:00 of the activation date in the customer's timezone. Example: For a license type that references `user_id`, activate a license for *John Doe* by inputting `john_doe` as the license value. Orb will then attribute that usage to the license associated with `user_id` = `john_doe`. This can be done in-app, or programmatically via [APIs](https://docs.withorb.com/api-reference/license/create-a-new-license-for-a-user). Licenses activated immediately become status “Active. Licenses with a scheduled activation date in the future become status “Inactive”, but will become “Active” on the activation date. License8 ## License deactivation Deactivating a license in Orb is the mechanism by which usage will stop being attributed to that license at the **end** of the current day in the customer’s timezone. Licenses9 # License credit allocation mechanics ## Proration behavior License allocations are not prorated by default and are aligned to the billing period. Activating a license at any point during the period grants the full allocation amount. ## Interaction with invoice adjustments [Adjustments](https://docs.withorb.com/product-catalog/adjustments#applying-adjustments-and-discounts) (discounts, minimums, maximums) only apply to the non-license eligible usage subtotal. Any usage covered by license allocations will not be impacted by any invoice- or price-level adjustments. This is to ensure that: * Adjustments, like discounts, have the maximum benefit or reduction on what you actually pay * Keep invoices clear and easy to reconcile ## Credit deduction order For usage tied to an active license, Orb always applies the license allocation first.  Once that allocation is consumed, Orb falls back to applying standard (non-license) credits based on its [standard deduction order](https://docs.withorb.com/product-catalog/prepurchase#credit-deduction-order). When no credits are available, Orb will bill usage as overage. ## Configurable overage write-off Usage that is not covered by the license allocation can be optionally written off.  This lets you programmatically forgive small amounts of license-attributed overage without charging end customers, or drawing down from the standard (non-license) credit pool. It is designed to alleviate latency leakage when managing entitlements, or to forgive promotional / trial usage. To configure, check the “write-off overage” setting in the license price. Licenses5 Any license overages incurred will be waived on the invoice, and accounted for in Orb Reports as contra revenue. Licenses6 This setting is scoped to license allocations only. It does not impact non-license overage. ## Revenue recognition License allocation credits carry a [cost basis](https://docs.withorb.com/product-catalog/prepurchase#cost-basis) of \$0. When applied, there is no revenue impact. Instead, the license price is recognized as revenue prorated over the service period, similar to a fixed fee in Orb. Read more about Orb’s [revenue recognition methodology](https://docs.withorb.com/revenue-reporting/revenue-recognition) here. # Operator’s guide To configure and manage licenses allocations: 1. Ensure that [usage ingested](https://docs.withorb.com/events-and-metrics/event-ingestion#events-and-metrics) into Orb to includes a license grouping key property. 2. Create a global License Type in [Settings](https://app.withorb.com/settings?tab=licenses). 3. Create a new plan, and add a License Price. 4. Add a license allocation, and define a credit allocation currency and amount per license. 5. Confirm which usage prices are eligible to draw down from the allocation. 6. Subscribe a customer to a plan with licenses. 7. Activate and manage licenses for that subscription to create the license allocation. 8. Usage will be billed on the invoice, in accordance with credit availability and deduction. When you add usage prices to a plan with a license allocation later on, include the new prices in the same change. Eligibility set earlier does not extend to them. # License configuration Source: https://docs.withorb.com/product-catalog/license-pricing # Introduction Licenses allow you to model generic entitlements in Orb, where usage can be attributed to a unique license for a billing period. The most common application of licenses is to model seat-based pricing in Orb, like for Anthropic Claude or OpenAI Codex, but can be extended to represent anything from bots, agents, workflows, machines. etc With licenses, you can: * Track per-seat activation rates * Report and display individual usage on your customer dashboards * Represent seat charges with a dedicated and more full featured billing abstraction Licenses4 # Key components ## License type A license type is a named pricing component that tells Orb how to attribute usage events to a licensed entity (such as a seat, bot, agent, workflow, machine, etc.). Each license type is defined by a license grouping key – this is a unique event property that identifies which license a usage event belongs to. Example: If your plans have multiple seat types (Pro, Developer, or Free), create one license type per seat type in Orb. Usage events with a key value pair of `pro_seat` = `john_doe` will attribute those events to John’s Pro license. License types are configured in [Settings](https://app.withorb.com/settings?tab=licenses), and can be globally used across all plans and subscriptions. Licenses1 ## License price A license price is a fixed-fee price that represents the cost of a single entitlement on a subscription. It is explicitly tied to a license type. It defines how much to charge per license per billing period, and the quantity of licenses to provision. Add a license price when [creating a new plan](https://docs.withorb.com/product-catalog/build-catalog) to provision licenses on subscriptions. Currently, you can have one license price per plan (and per subscription). License prices can only be configured on plans with fixed fees and unit-based usage prices. Licenses3 [Custom invoice grouping](https://docs.withorb.com/invoicing/structure#custom-invoice-grouping)**s** of usage line items is not available for plans with a license price at this time. ## Usage To attribute usage to a specific license, your usage events must include both the **license grouping key** defined by the license type, and the **license value** for the individual license as a defined property of the event. Orb uses this key value pair to determine which license a usage event should draw down from. Example: If your license type references `user_id`, a usage event for user *John Doe* should include: * **license grouping key:** `user_id` * **license value:** `john_doe` Sample payload: ``` { "idempotency_key": "", "event_name": "usage_event", "external_customer_id": "", "customer_id": "", "timestamp": "2026-01-01T00:00:00.000000Z", "properties": { "user_id": "john_doe" } } ``` Orb will then attribute that usage to the license associated with `user_id` = `john_doe`, upon license activation. For help [getting started with event ingestion](https://docs.withorb.com/events-and-metrics/event-ingestion#events-and-metrics), check out our docs. # License lifecycle management ## License management When a customer subscribes to a plan that includes licenses, the subscription automatically gains a Licenses tab. This tab is your command center for managing licenses. You can activate, deactivate, and track licenses across the subscription. It also serves as a reporting surface, showing all provisioned licenses and their current status in one place. Licenses4 ## License activation Activating a license in Orb is the mechanism by which usage will start being attributed to that license, given a unique license grouping key to register that license. Actual activation time is date aligned – any usage events ingested will be attributed to that license starting at 00:00 of the activation date in the customer's timezone. Example: For a license type that references `user_id`, activate a license for *John Doe* by inputting `john_doe` as the license value. Orb will then attribute that usage to the license associated with `user_id` = `john_doe`. This can be done in-app, or programmatically via [APIs](https://docs.withorb.com/api-reference/license/create-a-new-license-for-a-user). Licenses activated immediately become status “Active. Licenses with a scheduled activation date in the future become status “Inactive”, but will become “Active” on the activation date. License8 ## License deactivation Deactivating a license in Orb is the mechanism by which usage will stop being attributed to that license at the **end** of the current day in the customer’s timezone. Licenses9 # Operator’s guide To configure and manage licenses: 1. Ensure that [usage ingested](https://docs.withorb.com/events-and-metrics/event-ingestion#events-and-metrics) into Orb to includes a license grouping key property. 2. Create a global License Type in [Settings](https://app.withorb.com/settings?tab=licenses). 3. Create a new plan, and add a License Price. 4. Subscribe a customer to a plan with licenses. 5. Activate and manage licenses for that subscription. # Configure prepaid credits Source: https://docs.withorb.com/product-catalog/prepurchase In contrast to the in-arrears model of billing, where usage is invoiced at the end of a billing cycle after costs have been incurred, Orb supports deducting from a prepaid credit balance as usage occurs automatically. Prepaid credits are designed with the following principles: * **Real-time**: By default, Orb reflects changes to the credit balance as soon as events are ingested, with minimal latency. Depending on your desired deduction behavior, deductions to the balance can be configured to happen less frequently (e.g. if an hour’s worth of events are required before a deduction takes place). * **Auditable**: Orb provides an append-only trace of every balance-changing operation, including automatic usage deductions and manual credit operations. * **Immutable**: Orb provides an append-only ledger of transactions that affect the credit balance, which are immutable once finalized. Orb is able to accommodate late and out-of-order event reporting by allowing *pending* transactions for a fixed reporting grace period. * **Flexible**: Orb supports multiple pools of credits per customer, and prepurchase is fully compatible with all plan types and metrics. ## Structure of the credit balance Each customer has a credit balance in one *currency unit*, which could be a custom currency (“Acme Inc. credits”) or a real-world currency (e.g. “USD”). Structurally, the credit balance consists of blocks, which are initialized with an `amount` and `expiry_date`. At any given time, the total credit balance for a customer is the sum of the remaining amount in their unexpired credit blocks. ### Custom pricing units In addition to supporting commitments in a real currency (e.g. a \$50K commitment), Orb allows you to configure multiple custom pricing units, each with a corresponding ledger. This is useful for separating credits for different SKUs, allowing your customers to purchase credits for different products or services. For example, you might have a pricing unit for "Compute credits" and another for "Storage credits". When creating a plan, you can specify which pricing unit to use for each of the plan's prices, such that multiple separate line items might be priced in a combination of credit units, whereas the other might be in USD. On the customer page, you'll be able to see the current balance for each pricing unit, and the ledger will be filtered to only show transactions for that pricing unit. Each pricing unit will be set to have its own conversion rate to the plan's invoicing currency (e.g. USD). This conversion rate will be used to convert any overage usage to a chargeable currency. When creating standalone prices in Pricebook, you can also set the **Price currency** to a custom pricing unit. If you choose a custom pricing unit, specify an **Invoicing currency** and conversion rate so Orb can convert overage and invoice amounts into a real-world currency. To set up a custom pricing unit and enable its ledger, navigate to the "Pricing units" page in "Settings" and click "Enable custom pricing units". You'll be able to specify the identifier, display name, and short name of the pricing unit so Orb can display it properly in the UI and on invoices. Custom pricing units ### Cost basis Each credit balance block also tracks an optional *cost basis*. This represents the monetary value of each credit. It determines how much revenue is recognized in Orb's [Revenue Reporting](/revenue-reporting/revenue-recognition), and can help you track the original amount your customer paid for that balance. Costbasis1 1 Credits with a cost-basis of \$0 are typically provisioned as "promotional credits". They are granted at no cost to your end customer, and have no revenue reporting impact. Credits with a non-zero cost-basis carry an inherent value, and will be recognized as revenue when applied to usage. Orb requires the creation of an invoice when granting non-zero cost-basis credits, to ensure proper revenue reporting. If you invoice or handle payment of credits outside of Orb (i.e. marketplace customers), check "Mark this invoice as paid" to prevent duplicate invoicing effects. Costbasis2 ### Credit deduction order When usage draws down from a customer's credit balance, Orb deducts from credit blocks in a specific, deterministic order: 1. **License allocations:** for subscriptions with a license price, allocations created from active licenses will be applied to eligible usage, before drawing down from shared credit blocks. 2. **Blocks with item filters first**: If a credit block is scoped to specific items (e.g., only applicable to compute charges), it will be used before blocks without filters when those items are charged. 3. **Soonest-expiring blocks**: Among eligible blocks, those expiring soonest are used first to minimize credit expiration. 4. **Zero or no cost basis**: Blocks with zero or no cost basis are used before blocks with higher cost basis, preserving revenue recognition for paid credits. 5. **Earliest creation time**: If all else is equal, blocks created first are used first (FIFO). This ordering ensures that: * Dedicated license credits are applied to their intended charges * Scoped credits are applied to their intended charges * Expiring credits are used before they expire * Free/trial credits are consumed before paid credits **Prepaid credits only apply to in-arrears charges.** Credits cannot be applied to in-advance fixed fees. This includes both usage-based charges and fixed fees that are billed at the end of the period. ## Payment for credit balance Orb provides a single API to both charge customers via a connected payment provider (e.g. Stripe) *and* increment a customer's credit balance. This is useful for several reasons: * Provides a single action for triggering payment, avoiding inconsistent and tedious integrations with multiple payment providers. * Eliminates possible inconsistency and revenue reporting issues by ensuring that credit balance is available for customer use *as soon as* revenue is received. Without this functionality, if credit balance is added after payment is booked, end-of-month revenue recognition can be difficult or prone to errors. * Uses Orb metadata to directly tie an *increment* ledger entry to a payment provider action. ### Synchronous invoice issuance When an account has automatic invoice issuance enabled, Orb will attempt to issue credit purchase invoices synchronously where possible. This means that invoices for credit purchases will be generated and finalized immediately during the API call, providing faster processing and immediate confirmation of the transaction. However, there are certain scenarios where synchronous issuance is not possible: * **External provider sync**: If the invoice would be synced to an external invoicing provider (such as Bill.com, Stripe Invoicing, QuickBooks, or NetSuite), the invoice will be persisted and finalized asynchronously to allow for proper integration with the external system. * **Other asynchronous processes**: If other asynchronous processes that may attempt to take invoicing actions on the customer are running, the invoice will be persisted and finalized asynchronously to ensure system stability. In cases where synchronous issuance is not possible, Orb will handle the invoice processing in the background, and the invoice will be finalized through the standard asynchronous flow. ### Granting conditional on payment If successful payment is a *requirement* for credits to be effective, then pass `invoice_settings.require_successful_payment` when creating a credit increment. Note that this is an option *within* the `invoice_settings` dictionary because invoice creation is necessary for this behavior. With this option enabled, the credit block will be initialized with a `status` of `pending_payment` until the relevant invoice is paid. Although the block will be returned when fetching the customer credit balance, it will not be eligible to be drawn from until it's in `status=active`. If you're using Orb invoicing with a payment gateway integration, Orb is responsible for listening to the gateway webhooks and will automatically mark the block as active once the payment is successful. If the invoice is in a `synced` state (e.g. a different invoicing provider is used), it will need to be manually marked as paid via the API for the credit block to be considered part of the balance. Note that this option is also available when creating a credit top-up and is configured when first creating the top-up via the API. ## Running out of credit balance When a customer runs out of their credit balance, any excess usage they incur will be charged at the end of the month on the invoice. Instead, you might want to block customers from using your product until they do that. To do that, set up an alert: 1. Navigate to the customer’s page 2. In the Credits section, click Manage alerts 3. Add an alert for credit balance depleted if you want an alert when the customer’s credit balance reaches 0, or credit balance dropped if you want a non-zero threshold. Now, Orb will emit a `customer.credit_balance_depleted` or `customer.credit_balance_dropped` webhook anytime the customer’s credit balance goes below the specified threshold. To quickly test that the webhook is working, you can: 1. Click “Adjust credits” in the Credits section 2. Change the Adjustment type to “Subtract credits” 3. Enter the amount as the current credit balance for the customer and hit "Save" You should receive a credit balance depleted or dropped webhook, depending on the alert you configured. ### Automatic top-ups Orb supports automatic top-ups, which are triggered when a customer's credit balance falls below a certain threshold. This is useful for ensuring that customers have enough credit balance to cover their usage, and can be configured to automatically top-up by a fixed amount at a specific cost basis, including issuing an invoice. Automatic top-ups can be configured on a per-customer basis, and can be managed in the Orb web app or [API](/api-reference/credit/create-top-up). Automatic top-ups prevent you from having to build your own webhooks based top-up logic, which can be prone to race conditions and cause correctness problems (or unexpected overages for the customer). With Orb's automatic top-ups, you can be sure that the credit balance is always available for your customers to use and there are no in-arrears charges. Top ups ### Usage limits with top-ups Automatic top-ups don't include maximum usage limits. To enforce spending caps, track purchases via webhooks and deactivate the top-up using the [deactivate top-up API](/api-reference/credit/deactivate-top-up-by-external-id) when your limit is reached. For prepaid-only scenarios where all usage stops at zero credits, use the `customer.credit_balance_depleted` alert to block access. See [Running out of credit balance](#running-out-of-credit-balance). ## Allocations In addition to allowing on-demand credit purchases, customers can receive an allocation of credits, of one or more currency units, automatically as part of their subscription. Allocations can be configured in the "create plan" flow and overridden on a per-subscription basis, with an amount, optional rollover behavior, and cadence. Allocations While the set of usage-based prices on the subscription determines how Orb will deduct credits from the balance (or accrue them as overages on the invoice), allocations allow you to set up a recurring schedule of increments on the customer’s credit ledger. Because allocations are configured at a plan level, Orb allows you to standardize credit treatment and ensure consistency across thousands of customers. Just like a price, allocations can be configured to specify their cadence, or how often credits should be added. Once configured, a recurring allocation will immediately add future-effective credit blocks for the customer (corresponding to the billing date of the subscription), which can be previewed on the subscribed customer’s page. Allocation credit blocks can also be configured to expire at the end of the cadence (e.g. at the end of the month on a "Monthly" cadence), never expire, or expire after a set number of days or months. Allocation expiration Because allocations are tied to the subscription lifecycle, canceling a subscription will automatically prevent the customer from receiving new allocations and void any future credit blocks. Any backdated subscription actions (e.g. creation, cancellation, plan change) will affect the available credit balance according to the subscription timeline: the ledger will be replayed from the backdated modification point onwards, and past usage data will be deducted from allocated blocks active in the past. ### Allocation cost basis Allocation cost basis Allocations support both zero and non-zero cost basis configurations, enabling two distinct use cases: **Zero cost basis allocations** (default behavior) * Treated as free inclusions in the plan * Do not generate charges on customer invoices * No revenue recognition impact * Ideal for trial credits, promotional allowances, or plan-included usage **Non-zero cost basis allocations** * Specify a per-unit cost (e.g., \$0.50 per credit) * Generate charges on customer invoices, billed in-advance * Enable revenue recognition and accrual accounting * Useful for recurring commitments, contracted spend, or paid credit packages The cost basis is configured as a per-unit amount. For example, an allocation of 1,000 credits per month with a cost basis of \$0.50 per credit will result in a \$500 charge on each invoice (1,000 credits × \$0.50/credit = \$500). ### Invoice display and items When an allocation has a non-zero cost basis, it will appear as a line item on the customer's invoice. You can associate the allocation with a specific item to control how it's displayed on invoices and in your accounting system. If no item is specified, Orb will automatically create a default item based on the pricing unit (e.g., "Allocation (USD)" or "Allocation (API Credits)"). The charge for a non-zero cost basis allocation is billed in-advance at the beginning of each billing period, corresponding to when the credits are added to the customer's ledger. ### Example: Recurring commitment Consider a customer with a recurring monthly commitment to purchase 5,000 API credits at \$0.25 per credit: ``` Allocation amount: 5,000 credits Per-unit cost basis: $0.25 Cadence: Monthly ``` This configuration will: 1. Add 5,000 credits to the customer's ledger at the start of each billing period 2. Generate a \$1,250 charge on each invoice (5,000 × \$0.25 = \$1,250) 3. Enable revenue recognition as the credits are consumed 4. Allow the customer to use those credits against usage throughout the billing period If the customer uses all 5,000 credits and continues consuming your service, any additional usage will be charged as overage at the end of the billing period according to your usage-based pricing. The credit amount that a customer receives per billing cycle for an allocation is not prorated on billing cycle length. For example, if a subscription starts mid-month, the customer will still receive the full allocation amount for that initial period. Relatedly, if you remove an allocation from a subscription by editing the relevant interval on the subscription, credits will only be removed if the relevant period's line item *no longer exists at all*. This means that ending an allocation interval early in the billing period will not clawback a prorated subset of credits. ### Allocation credits vs purchased credits Credits behave differently during subscription changes depending on how they were granted: **Allocation credits** (recurring credits from plan allocations): * Are tied to the specific subscription plan that granted them * Expire immediately when you change or cancel the subscription * Future allocation blocks are voided when the plan changes **Purchased credits** (standalone credit blocks via the ledger API): * Persist across plan changes and subscription modifications * Only expire based on their configured `expiry_date` * Are not affected by subscription changes unless the subscription is backdated before the block's effective date **Example:** A customer on a Premium plan with a 1,000-credit monthly allocation downgrades to Basic (which has no allocation). The unused allocation credits expire immediately. However, if the customer previously purchased 5,000 credits separately via the [create ledger entry API](/api-reference/credit/create-ledger-entry-by-external-id), those purchased credits remain available. This distinction is important when designing trials or tier changes. If you need credits to persist through plan changes (e.g., trial credits that remain when upgrading to paid), grant them via the ledger API rather than as plan allocations. See [trials with prepaid credits](/self-serve/trial-management). ## Prepaid ledger Orb provides a ledger abstraction that acts as a log of a customer's credit balance over time. A ledger entry always indicates the starting and ending credit balance, as well as the time at which the transaction took place. New ledger entries are added in the following scenarios: * **Automatic deductions**: When usage is ingested, Orb will automatically deduct an amount from the credit balance if appropriate. To determine how much an incremental event should deduct from the credit balance, Orb uses both the billable metric (this determines the *quantity of usage* implied by the event) and its associated price (which determines the *amount* based on the quantity). Automatic deductions are tagged with a `price_id` since all deductions are associated with a specific price. * **Manual increments & decrements**: Through the Orb web app and API, you can manually add blocks to the balance (optionally specifying metadata like an expiry date), or deplete it. A manual increment always creates a new credit block and does not modify an existing block. To allow tracing these manual operations, Orb keeps track of the user or key that initiated the action, and also provides a `description` field for business-specific attribution. * **Block expiration**: When a block expires because its expiry date has passed, the credit balance is automatically depleted by the remaining `amount` in the newly expired block. * **Expiry changes**: It’s possible to change the expiration of all or part of a block manually; this will show up as a ledger entry but will not change the overall credit balance amount. ### Working with multiple currencies in the API Because ledgers can be in multiple currencies or pricing units, operations to the ledger endpoints are scoped to a specific currency. When you create a custom currency, you'll create an identifier, display name, and short name for the currency. Ledger operations through the API (e.g. [create ledger entry](/api-reference/credit/create-ledger-entry)) require that you pass the `identifier` for the `currency` field in the request body. ### Automatic usage deductions Orb records automatic deductions at a grouped level. Each day’s usage for a specific price, invoice, and credit block is represented as a single ledger entry. The `effective_date` of an automatic deduction is the *beginning* of the day in the customer's timezone. This means that a given credit block must be effective before the beginning of the day in order to be eligible for usage drawdown for that day's usage. In most circumstances, Orb recommends creating credit blocks aligned to the previous day boundary in the customer's timezone. ### Why deductions are grouped instead of event-level Orb does not guarantee a one-to-one mapping between usage events and ledger deductions for all pricing models. * Some pricing models are non-linear across a billing period (for example, bulk/volume repricing), so an individual event's effective rate may depend on total period usage. * Some adjustments (for example, minimums) are distributed across multiple line items during invoice calculation, so a fully adjusted per-event amount is not always defined until period close. * Backfills and amendments require deterministic replay of deductions and expirations. Grouped ledger entries keep replay efficient and predictable for high-throughput workloads. If you need event-level attribution (for example, by `user_id`, `api_key`, or `environment`), use [evaluate prices](/api-reference/price/evaluate-multiple-prices). This endpoint supports filtering and grouping over event properties and returns rated usage for the selected prices. Results may differ from finalized invoice totals when adjustments (such as minimums) apply across multiple line items. For invoice-accurate totals, use [subscription costs](/api-reference/subscription/fetch-subscription-costs) or invoice resources. **Credit deductions apply to all in-arrears charges**: While automatic deductions are commonly associated with usage-based pricing, prepaid credits can be applied to any charge that is billed in arrears, including fixed fees. For example, a monthly support fee or platform fee that is configured to bill at the end of the billing period (in arrears) will also draw down from the customer's credit balance. Only charges that are billed in advance are not eligible for credit deductions. ### Pending and committed ledger entries Events may be reported to Orb out of order and with significant lag, meaning that the reporting time does not necessarily match the `timestamp`, which is the time the usage occurred. To account for this, Orb maintains *pending* ledger entries until the [reporting grace period](/events-and-metrics/reporting-errors) has passed. After this point, Orb *commits* the entry, making it immutable. This is important to the fidelity of the ledger because the ordering of operations can have a significant impact to the credit balance. For example, if an event with `timestamp = 2022-02-02T23:00:00` is reported 10 hours later and a block expires at `timestamp = 2022-02-03T00:00:00`, Orb will automatically create the deduction ledger entry for the event *before* the block expiry, properly allowing it to deduct credit balance from an eligible block. Note that an entry that is pending is not the same as a block that is not active. The `status` of a block is only relevant when payment is a condition to adding credits; in contrast, a pending entry is related to the grace period of the ledger. ### Exporting the ledger In the admin web application, Orb provides a detailed ledger view. This view can be accessed from any customer page by clicking "View full credit ledger", and the ledger can be drilled down to specific operations, actors that took the action, and a cost basis threshold. This view also supports exporting these ledger entries to CSV format, which can be useful for further investigation or digging. Credit ledger ## Amendments to the credit balance In cases where manual adjustments need to be made to a customer’s credit balance, you can use manual increments and decrements. This is useful in cases such as: * Providing extra credits for trial purposes so that your customer has more usage to evaluate your offering * Compensating for downtime or reporting errors by adding a fixed amount to the credit balance In addition to these one-off adjustments, the prepaid credit balance will also reflect changes when historical usage is amended. For example, if you use Orb’s amendment functionality to overwrite events that previously led to credit balance deductions, Orb will automatically add or decrement credits to account for the difference between old and new events. Although historical amendments will lead to changes in the credit ledger, note that these amendments will not reflect in any issued invoices automatically. As a result, Orb does not recommend making amendments or backfills that overlap with issued invoices. You can also remove an entire credit block from a customer's ledger. Doing so will attempt to undo the creation of the credit block, meaning: * the credit block will be removed from the ledger, * invoices used to purchase the credit block will be voided, and * usage previously deducted from the removed credit block will be drawn down from other credit blocks, if available, or be counted as overage. ## Amendments to revenue recognition In cases where the customer requests a refund or in the case of a data input error, Orb supports amending the credit ledger in a manner that also maintains the consistency of the downstream [revenue recognition workflows](/revenue-reporting/revenue-recognition). For refunding a credit increment to your customer, Orb supports refunding a credit block via the "Void credits" operation on the dashboard and selecting the "Mark as refund" option. These show up in the ledger as "Refund" operations. For voiding a credit increment due to a potential client issue or data input error, Orb supports voiding a credit block via the "Void credits" operation and not selecting the "Mark as refund" option. These show up in the ledger as "Void" operations. # Executing a price change Source: https://docs.withorb.com/product-catalog/price-changes As your monetization strategy evolves, so will the items and price points on your standardized plans. Orb solves this change management problem in a safe and predictable way by introducing the concept of a plan "version" and a "subscription migration", which allows you to apply that version across a set of selected subscriptions. This is significantly more auditable than simply editing plans "in-place", which creates data reconciliation problems down the line. Orb's plan versioning feature is built on top of a few core principles: 1. **Safety through clarity**: At each part of the flow, Orb allows you to preview the effect of your changes. In the process of creating a new plan version, Orb will display a clear difference view to indicate the prices that have been removed, added, or changed. When configuring a migration, Orb will allow you to preview the effect of the migration for individual customers. 2. **Flexibility in scheduling**: Orb's invoicing backend is built to allow changes at any time in the future, immediately, or even in the past. Once scheduled, changes will apply without any manual intervention required. 3. **Simplicity for the end-user**: Orb's migrations functionality is specifically built to minimize the number of issued invoices which would create confusion for your end-users, and support requests for your team. For example, Orb supports changes 'at the end of the billing cycle', even if it's different for each user. Adding a new in-arrears usage based fee will automatically be consolidated with other fees that bill on the same cadence. Adding a new in-advance fee will bill for only that fee independently, and not fracture the entire subscription. 4. **Automation at scale**: Orb's migration functionality has been battle-tested over millions of subscriptions, and is built to handle both enterprise and self-serve use cases. ### Version 1 When you create a plan, the set of prices included in the plan will become your first version of the plan, that is: Version 1. When you publish your new plan, Version 1 will become the default version which means all new subscriptions created via the web or the API will use these price points. ### Creating a new plan version Each subscription in Orb is subscribed to a single version of a plan; namely, the version that was marked as the default version when the subscription was created. At any given time, there may be thousands of subscriptions subscribed to a plan, some on different plan versions. In order to edit a plan's prices, you'll need to create a new version that reflects those changes by selecting "Add version" on the plans page. Note that metadata edits (name and description) do not require a new version and are scoped to the Plan as a whole. You can edit these by selecting "Edit name and description" on the plan page. In Orb, you have a linear series of versions — each version builds on the last, so you'll always edit the most recent version to create a new one. Plan versions are identified by an increasing set of integer version numbers. When you create a new version you can: * **Add a price**: You can add a new price to the existing plan version. For example, you'd opt to do this if you're planning to roll out a new default-on usage-based SKU to everyone on your "Pro" tier, such that any usage that is now sent for that SKU into Orb should accrue on the relevant invoices. * **Edit a price**: This is the most common action in order to change parameters of your pricing — this may range from changing the unit price point, adding or removing tiers, or modifying the dimension values on a dimensional price. Although editing a price is functionally similar to removing followed by adding a price, Orb understands the concept of replacing explicitly. This is particularly helpful in the presence of subscriptions where a price has later been removed through an edit action. Because Orb understands that your versioning action is a replacement, the system will not re-add the price and will leave it removed. * **Remove a price**: Removing a price can be useful if you no longer want to charge for an item on your plan. * **Edit details**: You can also modify the maximums, minimums and discounts that are already on your plan. We recommend reviewing these when you change prices to make sure they're still applicable. ### Publishing a version After your version is reviewed and ready, you can publish the new version. When you publish the version, you have the option to make the new version Default or leave the existing default. When you make a new version the default, all new customers who subscribe to this plan will automatically be attached to these prices. Note that this is true of subscriptions created both via the Orb web-app as well as those created via the API, through any automations you have configured. This never affects existing subscriptions; in order to change prices for existing subscriptions you must migrate them to the new version (explained below). ### Alerts and versioning When you create a new plan version, Orb will automatically copy over alerts that were previously configured on prices that have not been edited or removed on the new version. If a price point has been edited or the price has been entirely removed, alerts will no longer trigger for subscriptions to the new version. You can and should modify your alerts before migrating any subscriptions over to your new plan version. Once a subscription is migrated to the new version, the new plan version alerts will be applied to the subscription. Orb will indicate if alerts were copied with a hint next to the Alerts tab. ### Migrating subscriptions to a new version Although publishing a version does not affect existing subscriptions, Orb allows you to mass-schedule these migrations without having to write any scripts, saving you time and the risk of incorrectly applied migrations. Orb allows you to migrate a subscription from one version to another with the following timing options, detailed below: * **Immediately**: The new version's set of prices will begin immediately and any existing prices from the old version will be ended. Since Orb uses day-based proration, immediate changes will take effect as of the beginning of the day in the customer's timezone for fixed fees, and lead to an immediate cutoff for usage-based fees. This will likely lead to the generation of a new invoice for each affected subscription, typically representing the accrual of any usage-based charges for the previous version and any newly added in-advance fees. Note, of course, that grace period allowances still apply before the invoice is issued. * **End of term** (shown as "Start of next term" in the Orb web app): This option takes advantage of the fact that Orb knows the billing period of each price across all your individual subscriptions. When this option is selected, the timing may vary for each subscription depending on its bill cycle day and cadences. The term is the maximum cadence across all the prices on the subscription — so Orb will not migrate the subscription until the term fully rolls over. In the typical scenario with only monthly prices, Orb will migrate the subscription at the end of its billing month, which may or may not align with the calendar month. * **Beginning of term** (shown as "Start of current term" in the Orb web app): Similar to end of term, this option uses the price billing periods of each individual subscription to backdate a migration to the beginning of the subscription's current term (the start of the current billing period for the price with the largest cadence). In the typical scenario with only monthly prices, Orb will migrate the subscription at the beginning of its billing month, which may or may not align with the calendar month. This option allows you to change usage-based pricing in the middle of the billing period without issuing unexpected invoices — for example, if you're billing monthly and want to launch a feature mid-month, you can just roll out the feature immediately and migrate subscriptions using the beginning of term option anytime in the month, and all of the usage of the new feature will be included in the end-of-month invoice. Note that adding or removing in-advance fixed fees can still generate a new invoice or void and reissue an existing one; see the invoicing impact table below. * **Start of current invoice period**: Like beginning of term, this option backdates the migration — but only to the most recent billing period start across the subscription's prices (the boundary where the current invoice period began), rather than the start of the longest-cadence price's period. For a subscription with an annual platform fee alongside monthly usage prices, beginning of term backdates the migration to the start of the annual period (potentially almost a year in the past), while start of current invoice period backdates it only to the start of the current monthly period. Orb will never backdate the migration before the start of the subscription or before an earlier plan change that took effect mid-cycle — if the most recent billing period start falls before either of those, the migration takes effect at that later point instead. * **Start of next invoice period**: Like end of term, this option schedules the migration for an upcoming billing period boundary — but for the soonest upcoming boundary across the subscription's prices (i.e. when the next invoice period begins), rather than waiting for the full term to roll over. For a subscription with an annual price alongside monthly prices, end of term defers the migration until the annual period ends, while start of next invoice period applies it at the next monthly rollover. Orb will never defer the migration past the end of the subscription or past an already-scheduled plan change — if the next boundary falls after either of those, the migration takes effect at that earlier point instead. * **Specific date**: Similar to 'immediately', Orb will start and end prices as of the selected date. Orb recommends using this if you have a communicated change date in marketing material. An *invoice period* is the span between consecutive billing period boundaries across a subscription's prices — the interval covered by each regularly scheduled invoice. For subscriptions where all prices share a single cadence and aligned billing periods, the invoice-period options are equivalent to their term counterparts. When cadences are mixed, keep in mind that the migration's effective time can fall in the middle of a longer-cadence price's period. This means that, unlike end of term, the invoice-period options do not guarantee that invoices are left untouched: draft invoices may be updated, and changes to in-advance fixed fees can generate new invoices or void and reissue existing ones. When determining these boundaries, Orb uses each price's regularly scheduled billing cycle — if a price's current period was truncated by an unrelated one-off change, the natural cycle boundary is used instead. Backdated migrations (to the start of term, the start of the current invoice period, or a specific past date) typically take longer to process since they require recalculating existing invoices. To ensure data consistency and finalization safety, if any invoices are scheduled to be issued during the migration process, Orb will prioritize migrating those subscriptions first before finalizing their invoices. This safeguard prevents any discrepancies between the migration and invoice finalization. Orb allows you to review the migration action in detail, both reviewing the aggregate action (e.g. number of subscriptions affected) as well as previewing how the migration will affect any given customer. Orb recommends that you spot-check individual customers to understand and confirm the effect of the migration. When a migration is scheduled, Orb provides a cancellation grace period before taking any action. During the grace period, the migration can be completely aborted without any consequences. Once the grace period elapses, Orb takes action by triaging each subscription and reflecting the new state. This happens regardless of the scheduled timing of the actual migration, because even future-changes must be reflected immediately on all subscriptions. This is an important property of the Orb system, and creates a predictable product experience. For example, when viewing a subscription's timeline or upcoming invoice, it's important that the future migration is properly accounted for. Once a migration has been reflected across all subscriptions, Orb does not perform any mass action at the time of the migration itself — this is already handled when the migration is first created. ### Scheduling multiple migrations Each migration applies only the changes its version introduced. A plan version migration applies the prices and adjustments that version added, removed, or replaced relative to the previous version. It affects only the version range it targets and takes effect exactly when you schedule it. Because each migration carries only its own version's changes, the order in which you trigger migrations doesn't change the result. This means you can schedule one for the end of term and run an unrelated migration immediately in the meantime. The exception is when one change depends on another: a later version can't replace or remove something an earlier version hasn't added yet. For example, if Version N adds a price effective Jan 20, a Version N+1 migration can't replace that price effective Jan 15 since at that point the price doesn't exist yet. Orb validates these cases and returns an error. ### Invoicing consequences of plan version migrations Unlike a plan change where prices are always fractured at time of plan change, Orb factors in prices that have not been changed when a subscription is migrated in order to avoid invoicing for that price unnecessarily. For example, if a single usage-based price is added in the new version, it will automatically align to the billing cadence of other prices and no prices will invoice when the migration is applied. Similarly, if only an in-advance fixed fee is added in the new version, that fee alone will invoice without causing disruptions to the billing periods of other usage-based or fixed-fee prices. In general, end of term pricing changes are the safest option, as they will never cause additional invoices to be generated. The table below shows the invoicing effect of different pricing change migrations depending on when in the billing period the migration is applied. A change to an existing price in a new version can be thought of as a removal of the old price and an addition of the new price. | Pricing Change | Migration Timing | Invoicing Impact | | --------------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Add usage-based price | Immediate | The new price starts accounting for usage immediately and will invoice with other usage-based fees at the end of the billing period. No additional invoice generated. | | Add usage-based price | End of term | The new price starts accruing at the beginning of the next billing period. No additional invoice generated. | | Add usage-based price | Beginning of term | The new price starts accruing from the start of the current billing period. Usage for the entire period will be included in the next invoice. No additional invoice generated. | | Remove usage-based price | Immediate | The old price will invoice immediately from the start of the billing period until the current time. | | Remove usage-based price | End of term | The old price will invoice until the end of the current billing period. | | Remove usage-based price | Beginning of term | The old price will invoice until the start of the current billing period. | | Add in-advance fixed fee | Immediate | A new invoice will be generated immediately for the prorated amount of the new fixed fee for the remainder of the current billing period. | | Add in-advance fixed fee | End of term | The new fee will be charged in full at the start of the next billing period. No additional invoice generated. | | Add in-advance fixed fee | Beginning of term | A new invoice will be generated dated to the start of the billing period to charge the full amount of the new fixed fee. | | Remove in-advance fixed fee | Immediate | The fixed fee will be removed from the subscription and a credit note will be applied to the invoice issued at the beginning of the billing period to prorate the fixed fee. No additional invoice generated. | | Remove in-advance fixed fee | End of term | The fixed fee will be removed from the subscription at the end of the current billing period. No additional invoice generated. | | Remove in-advance fixed fee | Beginning of term | The invoice at the start of the current billing period will be voided and a new one will be generated in its place without the removed fixed fee. | | Add in-arrears fixed fee | Immediate | The new fixed fee will be added to the subscription and a prorated amount will be invoiced for it at the end of the billing period. No additional invoice generated. | | Add in-arrears fixed fee | End of term | The new fixed fee will be added to the subscription at the end of the current billing period. No additional invoice generated. | | Add in-arrears fixed fee | Beginning of term | The new fixed fee will be added to the subscription at the start of the current billing period and will invoice at the end of the billing period. No additional invoice generated. | | Remove in-arrears fixed fee | Immediate | The fixed fee will invoice immediately prorated from the start of the billing period until the current time. | | Remove in-arrears fixed fee | End of term | The fixed fee will be removed from the subscription at the end of the current billing period. No additional invoice generated. | | Remove in-arrears fixed fee | Beginning of term | The fixed fee will be removed at the start of the current billing period. No additional invoice generated. | For the invoice-period timing options, the "Beginning of term" rows describe the impact of start of current invoice period and the "End of term" rows describe the impact of start of next invoice period — for each price whose own billing period boundary aligns with the migration's effective time. A price whose billing period contains the effective time mid-period (e.g. an annual price when the migration lands on a monthly boundary) is instead changed mid-period, as described in the "Immediate" rows relative to that effective time: a changed usage-based price is cut over as of the effective time, and a changed in-advance fixed fee is prorated — which can generate new invoices, or void and reissue existing ones. ### What does not change in a migration When migrating plans to a new version, Orb does not modify the following fields in the creation flow. These are also disabled in the plan creation flow in the UI. * Currency * Custom pricing units * External plan ID * Net payment terms * Plan name * Plan description ### Subscription changes after a migration is scheduled If you schedule a migration for a subscription and then subsequently change the plan or cancel the subscription, the price change will be overwritten. The latest action you take is always the one that is maintained. The current state of a subscription is displayed in the timeline of the customer page. As an example: if you schedule a price change on 4/1 that should take effect at the end of term on 4/15 and you cancel the subscription on 4/10, then Orb will cancel the subscription and no price change will occur on 4/15. A migration applies to subscriptions that are currently on the plan (and to new subscriptions that will start on it). It does not automatically update a subscription that has a pending plan change onto the plan that hasn't taken effect yet - that change keeps the version it was scheduled with. If you're moving all subscriptions to a new default, check for subscriptions with scheduled changes onto the plan and re-schedule or migrate them once the change is in effect. ### Migrating non-standard subscriptions In Orb, a subscription's set of prices can deviate from the plan it's subscribed to for a variety of reasons, which will impact the behavior of a plan version migration. Note that adjustments that have been applied ad-hoc to a subscription (e.g. via the price intervals API) always apply to a specific and stable set of price IDs. This means that prices that are added to a subscription through a plan version migration will not automatically be added to existing adjustments, and prices that have been replaced by a new price will also not automatically be added to the existing adjustments. In order to automatically apply existing adjustments to new prices (whether entirely added or replacement prices), please reach out to Orb support for a private preview of new functionality that will be available in the future. ### Subscription overrides | Override action taken | Change in new plan version | Migration behavior | | ----------------------------------------- | --------------------------------- | -------------------------------------------------------------------- | | Price A overridden, replaced with Price X | Price A replaced with Price B | Price X will remain on the subscription unchanged | | Price A overridden, replaced with Price X | Price A removed | Price X will remain on the subscription unchanged | | Price A overridden, replaced with Price X | Price C added to the subscription | Price C will be added to the subscription, Price X remains unchanged | | Price A removed | Price A replaced with Price B | Price B will not be added to the subscription | Some accounts may have subscriptions that were created with Orb's legacy overrides behavior, which dynamically creates an entirely new plan (sometimes called a child plan) to represent overrides rather than replacing the minimal set of prices and adjustments on the existing plan. Subscriptions on child plans cannot be migrated to new plan versions using the Orb web-app. If you'd like to migrate subscriptions on child plans to new plan versions, please reach out to support. #### Example: Subscription overrides Consider a plan with two prices: * Price A: A usage based fee charging on the number of API requests * Price B: A fixed fee charging for a seat-based charge If a subscription is created with the following overrides: * Price A is kept the same * Price B is removed entirely * Price C is added as a new fixed fee When the plan is updated with a new version that: * Replaces Price B with Price X Price X will not be added to the subscription, as Price B was overridden and removed. Price A will remain unchanged, and Price C will remain on the subscription. ### Add-ons After a subscription has been created, it can be edited with a subscription edit (which allows you to add/remove prices). This does not change the plan that the subscription is attached to. Migrations can be applied to subscriptions with edits, and Orb will automatically resolve the behavior in the presence of these edits. For example, if a price has been changed that is no longer on the relevant subscription, it will be unaffected. Similarly, add-ons that have been individually added to a subscription will not be removed even if that add-on is not explicitly in the new plan version, and plan prices that have been removed with subscription edits will not be added back to the subscription if modified in a new plan version. ### Fixed-fee quantities It's possible that fixed-fee quantities have changed on the subscription, independent of the default on the plan. If the same fixed-fee is present on the new plan version or the rate of the fixed fee has been changed, the quantity schedule will carry over untouched. ### Migration correctness and timing A migration can take many hours to fully complete processing all subscriptions – typically, migrations will process at around 2 seconds per subscription. However, because of the way they are designed, migrations are fully protected from billing period related timing race conditions even when they are not fully processed. When a migration is scheduled for a set of subscriptions, Orb ensures correctness: even if it's a "last minute" action before the end of the billing period, Orb will ensure that the new version's prices will be accurately reflected. Orb will apply the migration during the process of issuing an invoice if required, making sure that the old prices are never reflected past the effective date of a migration. This means that if you schedule a beginning of term migration on January 31st for a subscription that bills monthly, your invoices that issue on February 1st will reflect your updated pricing for the full month of January, even if the migration is still processing subscriptions. This may cause delays in the invoicing process, but ensures that the migration is correctly applied. ### Plan versioning workflows with webhooks In order to build custom workflows with plan versioning, Orb will issue a series of webhooks for each subscription – listen to the `subscription.plan_version_change_scheduled` and `subscription.plan_version_changed` webhooks to react to version changes in your own application. ### Plan changes vs. versioning Ask yourself whether your customers would think of the change you're planning as a "new service contract", or whether it's an iteration on your existing feature-set and price points. If the latter, plan versioning and migrations is an appropriate fit. If your use case truly represents an entirely new packaging (e.g. going from 2 packages to 4 packages), Orb recommends treating the new packages as net-new by creating new plans. Instead of versioning your plan, it's also possible to copy the current plan to a new plan and use plan changes to move customers to the new copy. ### Version selection for scheduled plan changes When you change a subscription onto a plan without specifying a version, Orb uses that plan's current default version. When the change is scheduled for a future date, that version is captured at the time you schedule it and does not update if the default changes before the change takes effect. For example, your Free plan's default is Version 2. On June 3 you schedule a subscription to move to the Free plan effective July 1. On June 9 you publish Version 3 as the new default. The change you scheduled on June 3 still takes effect on July 1 on Version 2, because the version is fixed when you schedule the change rather than resolved again on July 1. To put such a subscription on the newer default, either re-schedule the plan change after publishing the new default, or [migrate the subscription](#migrating-subscriptions-to-a-new-version) to the new version once the change has taken effect. To pin a specific version explicitly regardless of the default, pass `plan_version_number` when scheduling the change. ### Threshold Invoicing When a plan version migration adds, removes, or alters a price—whether immediately or as a backdated action—some threshold invoices may become orphaned. These orphaned invoices will still be visible to users in the invoicing portal but will be linked to a credit note if any of their prices are affected by the plan change. Any new threshold invoices created before the end of the billing period will be offset by the balance from the credit notes. If you do not check the box labeled "Allow Orb to correct past invoices and issue credit notes" when starting a migration, Orb will fail to migrate the subscription rather than attempt to orphan a threshold invoice. ### Canceling a migration If a migration is canceled during the grace period of 5 minutes, no action is performed. If the grace period has elapsed, canceling a migration will prevent new subscriptions from being put in a 'scheduled' state. However, this does **not** automatically revert any scheduled migrations in the future, as those upcoming changes have already been reflected on each subscription's timeline. In order to do so, you can schedule a new migration back to the original version. ### Migration checklist Migrations are a powerful tool to make changes across your entire customer base, and we recommend a careful approach to executing them. Run your migration over a sample of customers (e.g. 20) to double check the consequences of the migration before running it over all subscribers on your plan. Consider the following questions before running your migration: 1. Ensure that the *effective time* of your migration is what you intend. The effective time can have major consequences on whether invoices will issue. Especially if you have prices on your subscription with multiple cadences, remember that the 'term' concept refers to the price with the longest cadence (so a start or end of term may be up to a year in the past or the future). If you want the migration to align with the nearest billing period boundary instead, use the start of current invoice period or start of next invoice period options. 2. Be sure to understand the invoices or credit notes that will change as the result of your migration. Orb allows you to inline preview these changes when staging the migration on a per-customer basis. 3. Consider how different subscriptions may migrate differently. We recommend that you check subscriptions that have custom adjustments (i.e. those that have been added directly to the subscription), subscriptions on different phases (if your plan is phased), and subscriptions that may have had prices replaced, removed, or added. # Price configuration Source: https://docs.withorb.com/product-catalog/price-configuration The Price resource represents a price that can be billed on a subscription, resulting in a charge on an invoice in the form of an invoice line item. Prices take a quantity and determine an amount to bill. Orb supports a few different pricing models out of the box. Each of these models is serialized differently in a given Price object. The model\_type field determines the key for the configuration object that is present. ## Unit pricing With unit pricing, each unit costs a fixed amount. For example, \$0.50 per API call. ```json theme={null} { ... "model_type": "unit", "unit_config": { "unit_amount": "0.50" } ... } ``` ## Tiered pricing In tiered pricing, the cost of a given unit depends on the tier range that it falls into, where each tier range is defined by an upper and lower bound. For example, the first ten units may cost \$0.50 each and all units thereafter may cost \$0.10 each. The first\_unit is exclusive while the last\_unit is inclusive. ```json theme={null} { ... "model_type": "tiered", "tiered_config": { "tiers": [ { "first_unit": 0, "last_unit": 10, "unit_amount": "0.50" }, { "first_unit": 10, "last_unit": null, "unit_amount": "0.10" } ] } ... ``` ## Bulk pricing Bulk pricing applies when the number of units determine the cost of all units. For example, if you've bought less than 10 units, they may each be \$0.50 for a total of \$5.00. Once you've bought more than 10 units, all units may now be priced at \$0.40 (i.e. 101 units total would be \$40.40). ```json theme={null} { ... "model_type": "bulk", "bulk_config": { "tiers": [ { "maximum_units": 10, "unit_amount": "0.50" }, { "maximum_units": 1000, "unit_amount": "0.40" } ] } ... } ``` ## Package pricing Package pricing defines the size or granularity of a unit for billing purposes. For example, if the package size is set to 10, then 4 units will be billed as 10 and 11 units will be billed at 20. ```json theme={null} { ... "model_type": "package", "package_config": { "package_amount": "0.80", "package_size": 10 } ... } ``` ## Dimensional pricing Dimensional pricing defines a set of unit prices in a one , or many dimensional matrix. `dimensions` defines the event property values evaluated in this pricing model. For comprehensive guidance on dimensional pricing, see the [dimensional pricing guide](/product-catalog/dimensional-pricing). ```json theme={null} { "model_type": "unit", "unit_config": { "unit_amount": "0.50" } ... "dimensional_price_group_config": { "external_dimensional_price_group_id": "compute", "dimension_values": ["region", "instance_type", "cloud_provider"] } ``` ## License pricing License pricing allows you to model generic entitlements in Orb, where usage can be attributed to a unique license for a billing period. The most common application of licenses is to model seat-based pricing in Orb, but can be extended to represent anything from bots, agents, workflows, machines. etc Each license can include its own allocation of credits to handle proper billing, fair-usage enforcement, reporting and alerting per entitlement, while still participating in the shared subscription credit pool and standard account-level overage billing. For comprehensive guidance on license pricing, see the license management docs. ```json theme={null} { ... "model_type": "unit", "unit_config": { "unit_amount": "10.00" }, "license_type_id": "", ... } ``` ## Matrix pricing [*******Matrix pricing is deprecated and only supported for legacy accounts. We recommend switching to dimensional pricing, which offers better performance and supports unlimited dimensions - unlike matrix pricing, which is limited to two. To learn how to migrate, click here.*******](/product-catalog/dimensional-pricing#migration-from-matrix-pricing) Matrix pricing defines a set of unit prices in a one or two-dimensional matrix. `dimensions` defines the two event property values evaluated in this pricing model. In a one-dimensional matrix, the second value is `null`. Every configuration has a list of `matrix_values` which give the unit prices for specified property values. In a one-dimensional matrix, the matrix values will have `dimension_values` where the second value of the pair is null. If an event does not match any of the dimension values in the matrix, it will resort to the `default_unit_amount`. Importantly, the values for the `dimension` properties sent in the event should always be sent as a **string** i.e with quotes around them. For example, the pricing is that any call will be \$3.00, but if the call is made in the alpha or west region, it will be \$2.00. ```json theme={null} { "model_type": "matrix" "matrix_config": { "default_unit_amount": "3.00", "dimensions": [ "cluster_name", "region" ], "matrix_values": [ { "dimension_values": [ "alpha", "west" ], "unit_amount": "2.00" }, ... ] } } ``` ## Billing timing and cadence Every price has two timing-related configurations that control when charges appear on invoices: ### Billing mode The billing mode determines *when within a billing period* the charge is calculated: | Billing mode | When charged | Use for | | -------------- | --------------------------- | ------------------------------------------- | | **In-arrears** | End of the billing period | Usage-based charges, metered fees | | **In-advance** | Start of the billing period | Platform fees, seat licenses, subscriptions | **Credit eligibility**: Prepaid credits can only be applied to in-arrears charges. In-advance fees are charged immediately and do not draw from prepaid credit balances. ### Cadence The cadence determines the *length of the billing period*: | Cadence | Billing period | Typical use | | --------------- | ----------------- | ---------------------- | | **Monthly** | Calendar month | Standard billing | | **Quarterly** | 3 months | Enterprise commitments | | **Semi-annual** | 6 months | Large contracts | | **Annual** | 12 months | Annual subscriptions | | **One-time** | Single charge | Setup fees, credits | | **Custom** | Specified in days | Non-standard terms | Billing mode and cadence are independent. A monthly price can be billed in-advance or in-arrears. An annual commitment can be billed monthly (in-arrears usage) or annually (in-advance platform fee). ## Fixed fees Fixed fees are prices that are applied independent of usage quantities, and follow unit pricing. They also have an additional parameter `fixed_price_quantity`. If the Price represents a fixed cost, this represents the quantity of units applied. ```json theme={null} { ... "id": "price_id", "model_type": "unit", "unit_config": { "unit_amount": "2.00" }, "fixed_price_quantity": 3.0 ... } ``` # Scoped credit blocks Source: https://docs.withorb.com/product-catalog/scoped-credits **This feature is in early access.** Scoped credits are currently available to a limited set of customers. If you're interested in enabling this feature or have feedback, please reach out to your Orb account team or [contact support](https://support.withorb.com/). ## Overview Scoped credits allow you to control how prepaid balances are consumed across different products and SKUs. When creating a credit block, you can scope it to specific [items](/core-concepts#item) (products or SKUs in your catalog) so those credits apply only to usage from those items. Unscoped credits remain global and apply to all in-arrears usage and fixed fees in the same currency. ### Key benefits * **Product-specific budgets**: Allocate dedicated credits to specific products or services (e.g., separate budgets for "API Calls" vs. "Storage") * **Automatic fallback**: Scoped credits are applied first to matching items. Once scoped credits are exhausted, usage automatically draws from unscoped credits * **Flexible credit hierarchies**: Mix product-specific and organization-wide credits to match your business model ### When to use scoped credits **Use scoped credits if:** * You have customers using multiple products who need separate budgets per product * You need fine-grained control over how prepaid balances are consumed * You want product-specific credits to be exhausted before drawing from a general pool **Skip scoped credits if:** * You prefer all credits to apply uniformly across all usage * Your customers use a single product or SKU ## Quick start The most common use case is creating credit blocks that can only be used for specific items. Here's how that works: 1. **Create a scoped credit block**: Grant \$1000 in credits scoped to your "API Calls" item 2. **Customer incurs charges**: When the customer uses your API, those charges draw from the scoped credits first You can create scoped credits through the Orb dashboard or API (see [Configuration](#configuration) below). The key is specifying which items the credits apply to when you create the credit block. ## How scoped credits work When you create a [credit block](/product-catalog/prepurchase), it exists in a specific currency (like USD or a [custom pricing unit](/product-catalog/prepurchase#custom-pricing-units)). By default, any credits in that currency can be used for any in-arrears usage and fixed fees in the same currency. Scoped credits let you add an additional layer of control: you can filter which items are eligible to draw from a specific credit block. For example, you might create a \$500 credit block and scope it to "API Calls" usage. This means the credits can only be used for API Calls charges, not for Storage or other items. ### Priority logic When a customer incurs a charge, Orb looks at all available credit blocks in the same currency and applies them in this order: 1. **Scope**: Scoped credits (filtered to matching items) take priority over unscoped credits 2. **Expiration**: Among credits with the same scope, those expiring sooner are used first 3. **Cost basis**: Among credits expiring at the same time, cheaper credits are used before more expensive credits **Example**: A customer has \$500 in credits scoped to "API Calls" (expires March 31) and \$1000 in unscoped credits (expires March 15). When they incur API usage charges on March 1, Orb applies the "API Calls" credits first (scoped takes priority over unscoped), even though the unscoped credits expire sooner. ## Common scenarios ### Overlapping scoped credits When an item appears in multiple scoped credit blocks, Orb uses the priority logic to determine which credits to apply first. **Setup:** * Credit block 1: \$500 scoped to "API Calls" only (expires March 31) * Credit block 2: \$1000 scoped to "API Calls" and "Storage" (expires March 15) * Customer incurs \$1200 in API usage charges **Result:** Orb applies credits from block 2 first because it expires sooner (March 15 vs. March 31). Once block 2's \$1000 is exhausted, the remaining \$200 is deducted from block 1. Both blocks are scoped, so the expiration date is the tiebreaker. ### Scoped and unscoped credits working together You can mix scoped and unscoped credit blocks in the same currency. Scoped credits always apply first, then usage falls back to unscoped credits. **Setup:** * Credit block 1: \$500 scoped to "API Calls" (expires March 31) * Credit block 2: \$1000 unscoped (applies to all items, expires March 15) * Customer incurs \$600 in API usage charges **Result:** * The first \$500 is deducted from block 1 (scoped to "API Calls") * The remaining \$100 is deducted from block 2 (unscoped) ## Modifying scoped credit blocks Once you create a scoped credit block, you cannot add or remove items from its filter. Plan which items to scope carefully before creating credit blocks. **Workaround**: If you need to change the scope: 1. Void the existing credit block 2. Create a new credit block with the desired item scope 3. Note that this creates a new block, so any cost basis or metadata from the original block will need to be recreated **Best practice**: When setting up scoped credits for a customer, consider their full product suite and whether they're likely to add products in the future. If uncertain, use unscoped credits initially and transition to scoped credits once their usage patterns are established. ## Configuration ### Plan-based allocations When creating or editing a plan, you can configure recurring credit allocations to be scoped to specific items. In plan settings, navigate to "Add allocation" and use the "Filter builder" to choose which items the credits apply to: Allocation Pn Add Allocation Pn The filter builder lets you include or exclude specific items. The list of available items is pulled from all items on the plan. If you don't select any items, the allocation applies to all items in the plan. Addfilter Pn ### Customer-level credit grants For one-time credit grants (such as prepaid purchases, trials, promotions, or compensation), you can add item filters at the customer level. These scoped credits apply to all subscriptions under that customer. Learn more about [managing credits](/enterprise/managing-credits). Use the "Filter builder" to scope which items can draw from the credit block. The list of available items pulls from your entire account catalog. Creation Pn ### API configuration For programmatic control, you can include item filters when creating credit blocks and allocations through the API. Use the API when you need to automate credit provisioning based on customer actions or integrate credit management into your application's workflow. To create a scoped credit block, add a `filters` array with `item_id` filters when [creating a ledger entry](/api-reference/credit/create-ledger-entry). Filters support `includes` (credits apply only to specified items) and `excludes` (credits apply to all items except specified ones) operators: ```json theme={null} POST https://api.withorb.com/v1/credits/ledger_entry { "metadata": {}, "currency": "USD", "entry_type": "increment", "amount": 500, "expiry_date": "2025-03-31", "effective_date": "2025-03-01", "per_unit_cost_basis": "0.10", "filters": [ { "field": "item_id", "operator": "includes", "values": ["FszQj7VqS3bbqcEo"] // Item ID for "API Calls" } ] } ``` You can also scope recurring credit allocations when [creating a plan](/api-reference/plan/create-plan) by adding filters to the `allocation_price` configuration. To view a customer's credit blocks and their filters, use the [fetch credits endpoint](/api-reference/credit/fetch-customer-credit-balance). # Subscription plan changes Source: https://docs.withorb.com/product-catalog/subscription-plan-change As your customer continues using your product, they may choose to change their level of service, requiring a change to their billing configuration. This is most common when a customer is either *upgrading* or *downgrading* plans, but can also be necessary when any pricing terms durably change (e.g. the addition of a new usage metric to an existing plan, or a new signed commitment requiring a minimum to be added to the plan). When you execute a subscription change, Orb internally *versions* the Subscription, and continues to provide an audit trail of the previous state and plans of the Subscription. Since Orb's invoices always correspond to a *single* plan, a plan change may result in two invoices being issued: an invoice with charges on the old plan, as well as an up-front invoice for the new plan. **Subscription history is preserved**: When you change plans, the subscription ID remains the same, but Orb maintains a complete audit trail of every plan assignment. This allows you to see what plan a customer was on at any point in time and ensures accurate billing and revenue recognition. Plan change ### Prorations for in-advance fees If a plan change is for a customer that has already paid an in-advance fee, Orb will credit the customer an amount corresponding to the portion of the time period that was left unused using day-based proration. This amount will be added to the customer balance, which will be used by future invoices. When applying a subscription change with `mark_as_paid=true` (indicating payment has already been collected), invoices are finalized directly to paid status without syncing to external invoicing providers or consuming existing customer balance. This workflow is designed for scenarios where payment has been collected out-of-band, such as through a checkout flow. Consider the following plans table: | Plan Name | In advance fee | | ------------ | -------------- | | Beginner | \$50 | | Intermediate | \$100 | | Advanced | \$500 | The following timeline illustrates the prorations, assuming that all subscriptions bill on the first of the month and prorate accordingly: | Date | Manual actions | Orb behavior | | ---------- | ------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | 07-01-2023 | Subscription started to Intermediate |
  • Orb issues an invoice for \$100.
| | 07-04-2023 | Changed from Intermediate to Advanced |
  • Orb issues an invoice for any usage accrued on Intermediate.
  • Orb issues a credit note on the 07-01-2023 invoice to refund for 28 days that were not used on Intermediate (\$90.32).
  • Orb issues an invoice for 28 days of utilization of Advanced (\$451.61), and utilizes the full customer balance to charge \$361.29.
| | 07-11-2023 | Changed from Advanced to Beginner |
  • Orb issues an invoice for any usage accrued on Advanced.
  • Orb credits the customer balance for 21 days that were not used on Advanced, out of the original 28 days paid (\$338.71).
  • Orb issues an invoice for 21 days of utilization of Beginner (\$33.87), and draws down the customer balance to charge \$0. The remaining customer balance is \$304.84.
| ### Scheduling plan changes Orb's web application supports changing a subscription's plan immediately, on a requested future date (e.g. the *start of a new contract*), or at the end of the subscription's current term (e.g. if the term represents a customer's current commitment). This also provides an opportunity to change the subscription's billing date, which can be modified to be the start of the *new* plan. Once a plan change has been scheduled, Orb will honor the change over any scheduled cancellations. Change plan # View subscription usage Source: https://docs.withorb.com/product-catalog/usage-visibility A key benefit of Orb is the ability to understand the utilization of your customers as it happens, rather than with a manual or batch process. Visibility into accrued usage can help you identify trends among your customers, assess account health, and provide insights into business opportunities. ## Usage visualizations A subscription detail page presents usage in multiple formats: * Orb's real time usage graphs show quantities split by day for each billable metric connected to the subscription. This view always displays fresh results on the basis of all events that have been ingested by the system. If your subscription's plan charges for compute and storage costs, you will be presented a separate daily graph for both metrics. These graphs also allow you to view historical usage up to 90 days in the past, and compare trends with the previous billing cycle. * The invoice preview shows the upcoming invoice resource for this billing period, with details on fixed fees, usage quantities, and the total amount for each line item. When working with a prepaid plan, the invoice preview will also show deductions to the balance over the current billing period. * Historical invoices show finalized invoice PDFs. Invoice PDFs contain a breakdown of all line items along with billing metadata, tax information. Note that once an invoice PDF is finalized, its amounts are frozen. For auditing reasons, Orb will not dynamically regenerate a finalized invoice. View usage ## Programmatically receiving usage Orb's [view subscription usage endpoint](/api-reference/subscription/fetch-subscription-usage) enables you to retrieve usage over time in a structured format. This API is quite flexible, allowing you to optionally group usage for a metric by an event property (e.g. to view compute per region), query over custom time ranges, and to window usage. Retrieving usage can be especially useful to power in-product visibility for your end customers. Giving your customers the ability to understand their utilization in real time can significantly reduce your customer support volume due to surprising invoices with high utilization amounts or overages. ## Usage-based triggers In order to automatically action based on usage trends, Orb provides [webhooks](/integrations-and-exports/webhooks) and natively supported workflows such as automatic top-ups for prepaid plans. # Quickstart overview Source: https://docs.withorb.com/quickstart-guide Build your first usage-based pricing model in Orb. This quickstart walks through event ingestion, metric definition, pricing configuration, and invoice generation—showing how Orb's query-based architecture supports flexible, auditable billing. ## What you'll build A complete usage-based billing workflow: * Ingest raw usage events (API calls, compute hours, or any measurable activity from your product) * Define a billable metric as a query over your events * Configure pricing using the metric (per-unit, tiered, or volume-based) * Create a customer subscription linking them to your pricing * Generate an invoice calculated from actual usage ## Prerequisites Before starting, ensure you have: * **Orb account** — Test mode recommended for this quickstart. [Sign up](https://www.withorb.com/contact-us) if you don't have an account. * **API token** — Generate from [Connections → API tokens](https://app.withorb.com/settings?tab=developer). Required for API requests. * **Your usage data schema** — Understand what events you'll send and what properties they'll include. * **30 minutes** — Estimated time to complete all steps. ## The implementation path This quickstart follows the natural data flow in Orb's query-based architecture: ### 1. [Ingest events](/quickstart/ingest) Send raw usage data to Orb. Events are stored immutably as the foundation for all billing calculations. ### 2. [Create metrics](/quickstart/metrics) Define billable metrics as queries over your events. See how metrics can be changed without re-ingesting data. ### 3. [Configure pricing](/quickstart/pricing) Build your pricing model using metrics—per-unit rates, volume tiers, or custom structures. ### 4. [Create subscriptions](/quickstart/subscriptions) Link a customer to your pricing. Orb begins tracking usage and calculating charges in real-time. ### 5. [Set up invoicing](/quickstart/invoicing) Generate your first invoice from actual usage. Understand how invoices are deterministic queries, not side effects. ### 6. [(Optional) Explore simulations](/simulations/introduction) Run "what-if" scenarios to test pricing changes against historical usage before deploying them. ## What you'll learn * Why sending raw events once lets you change billing metrics anytime without re-instrumentation * How to test pricing changes against real usage data before deploying them to customers * Why late-arriving data and backfills work without manual reconciliation or invoice corrections * How to trace every invoice charge back to its source events for complete audit trails * The foundation for Revenue Design workflows that eliminate cross-functional coordination ## Success criteria By the end of this quickstart, you'll have: * Events flowing into Orb via API * A billable metric calculating in real-time * A pricing plan using your metric * A customer subscription tracking usage * An invoice generated from actual usage data * Understanding of how query-based architecture supports this workflow ## Ready to start? Begin with event ingestion: [Ingest events →](/quickstart/ingest) Use test mode for this quickstart. Test mode is completely isolated from production and safe for experimentation. You can always promote configurations to live mode later. # Ingest events Source: https://docs.withorb.com/quickstart/ingest ## What's in an event? An event is a primitive in Orb and is the basis for any usage-based pricing in your strategy. [Unlike subscription billing providers](https://www.withorb.com/blog/metered-billing-vs-usage-based-billing-what-are-the-key-differences) which provide metered billing, usage reporting in Orb isn’t simply a single aggregate value that the system consumes periodically and multiplies with a price amount. Orb's events model makes it easy to send in a raw stream of data, and allows you to construct queries on that stream in the product. Not only is sending in a set of raw events less upfront work for you and your team, it avoids re-integration when your billing strategy changes. Think of events as the rows in a database table. Once you've ingested events, you configure queries ("billable metrics") over those rows, which are automatically materialized by Orb as new events stream in. Customers typically send hundreds or thousands of events a minute to Orb, which prevents your team from maintaining expensive aggregation data infrastructure. **Why immutable events matter:** Unlike streaming aggregation systems that lock billing logic at ingestion time, Orb's immutable event store enables cross-functional flexibility. Engineering sends raw events once. Product defines and iterates on metrics without re-ingestion. Finance gets complete audit trails showing how every charge originated. Sales can apply backdated contract pricing to historical usage. You'll want to put thought into designing your events, but let's get a couple events into the platform right away. ## Ingesting your first event To get the ball rolling, we'll use Orb's batch events API to ingest two example events. First, we’ll need an API key so Orb can ingest events into your account. Orb’s API uses `Bearer` or Token authentication. To start, ensure that you're in Test Mode and generate an API key for testing by going to the Orb webapp > Organization Settings > Create new API key. Even though you haven't set up any customers in Orb just yet, we can still start sending in some events. Events in Orb must be tagged with a timestamp, so let's first get the current time as an ISO 8601 string. In your terminal, run: ```bash theme={null} date -u +"%Y-%m-%dT%H:%M:%SZ" ``` This should return something like `2023-02-20T00:00:00Z`, which is an ISO formatted current timestamp. Copy this value so we can use it in the next step. Paste the following cURL command into your terminal, replacing the `` with your copied API token and `` with the timestamp you just generated. ```bash theme={null} curl --location --request POST 'https://api.withorb.com/v1/ingest?debug=true' \ --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data-raw '{ "events": [ { "idempotency_key": "ingesting-events-guide-1", "external_customer_id": "demo_customer", "event_name": "function_compute", "properties": { "app_id": "DO1X6mLzDPa7", "execution_ms": 55737, "function_id": "hLvenfMutA5j", "provisioned_memory_gb": 8 }, "timestamp": "" }, { "idempotency_key": "ingesting-events-guide-2", "external_customer_id": "demo_customer", "event_name": "function_compute", "properties": { "app_id": "aMwoQ3ldnJXA", "execution_ms": 23647, "function_id": "g6OidvuWCvT8", "provisioned_memory_gb": 8 }, "timestamp": "" } ]}' ``` As response, you’ll see: ```json theme={null} { "debug": { "duplicate": [], "ingested": ["ingesting-events-guide-1", "ingesting-events-guide-2"] }, "validation_failed": [] } ``` As that response suggests, we've ingested in two events (which are identified by their idempotency keys) and there were no errors. To see this in the Orb platform, head over to the ["Events" tab](https://app.withorb.com/events?time_range=last_4h\&page=1) under the "Developers" section in the sidebar in your test mode account in Orb and take a look. You should see rows in the table that correspond to these two events. If you click in for more details, you'll see a timeline of what processing Orb has done on these events— for now, they don't match any real customers in the platform so you'll just see that they've been ingested at the relevant timestamp. Events tab Before we move on, let's just run that same cURL command one more time to see what happens! With this second run, you'll see: ```json theme={null} { "debug": { "duplicate": ["ingesting-events-guide-1", "ingesting-events-guide-2"], "ingested": [] }, "validation_failed": [] } ``` Orb doesn't throw any validation errors, but it does indicate that the events have already been ingested — this idempotency property is a very important part of building a billing events pipeline. You want to make sure you never double count the same usage! We'll dive deeper into this in the sections below. ## The event schema As you can tell from our example above, you don't need to do any setup to start sending in events. Orb doesn't require you to declare your event schema upfront or have your event body conform to a specific shape. Only the following restrictions apply: * Each event must contain a few first-class fields, including an `idempotency_key` (for deduplication), a customer identifier (for attribution), and a timestamp (to assign the event to an appropriate billing period). * Events also have a schema-less dictionary of key/value pairs. The values in these pairs must be primitives (numbers, strings, or booleans) with no nesting allowed - arrays and objects are not permitted. Numeric values must be between `-9223372036854775808` and `9223372036854775807`. An **idempotency key** helps Orb determine which events are conceptually duplicates and should not be ingested multiple times — after all, in normal use, you may have lots of events that look exactly the same to Orb but you do want to count them multiple times! Idempotency keys should therefore be unique to the 'occurence' or the action in your product. For example, if every transaction in your system has a corresponding UUID, you can use that UUID as the idempotency key. If you're ingesting events from a database, you can use the primary key of the table as the idempotency key. Note that idempotency key deduplication is guaranteed only during the [grace period](https://docs.withorb.com/guides/events-and-metrics/reporting-errors#reporting-grace-period) window. ## Types of events Typically, events map to one of the following: 1. User action: When a user triggers an action in your application, you may want to send an event to capture a record of it. Depending on your use case, this might capture a user login, file download, triggered verification, or a refreshed dashboard. 2. Background task: In many cases, a user action will trigger an asynchronous task that executes in the background. For example, you might log an event every time a job finishes, an image is processed, or a model is trained. 3. Regular heartbeat: Especially if you’re an infrastructure provider, you may want to send an event over a regular time interval that indicates that a resource is active, alongside any metadata over that timeframe. ## Productionizing your events pipeline Orb offers plenty of different ways to send in data, ranging from the direct API integration to solutions that involve regular exports from your existing data warehouse via a durable message bus like S3. To learn in more detail about these approaches and dive into event ingestion, check out the [ingestion guide](/events-and-metrics/event-ingestion). # Invoice your customers Source: https://docs.withorb.com/quickstart/invoicing **Why deterministic invoices matter:** Invoices in Orb are deterministic queries, not side effects. Unlike streaming systems where invoices are calculated once (and corrections require manual intervention), Orb's invoices are reproducible calculations. Finance can backfill late usage data and Orb automatically recalculates affected invoices. Product can test pricing changes with exact invoice previews. Every line item traces back to specific events and pricing rules—complete auditability by design. Now that you've [subscribed your customer to a plan](/quickstart/subscriptions), you're ready to set up your customer's invoicing experience. We always recommend first testing these workflows in Orb's dedicated test mode to ensure that you understand the side-effects of actions before you deploy them in production. Orb isn't just a billing platform — it also ships with native, transparent invoicing that allows you to issue invoices to your customers and collect payment. Orb Invoicing integrates with payment gateways like Adyen and Stripe to allow you to collect payment via credit card or ACH. ## Configuring invoicing metadata The details section at the top of your invoices doesn’t look very complete yet, which can lead to a confusing experience for your customers and cause issues from a compliance perspective. Let's fix that by setting up your company details and your customer's address. Set up your company details in the [Invoice settings tab](https://app.withorb.com/settings?tab=invoices). The company details will be used when showing an invoice in the Orb invoice portal and will also be reflected when generating invoice PDFs. Let's also fill in the customer's address on the customer detail page by clicking 'Edit' next to the Addresses section. ## Testing invoice email delivery Orb sends invoice emails to your customers directly on your behalf, linking directly to the invoicing portal. This link is an invoice's customer-facing link as opposed to the internal dashboard link you've been exploring in this quickstart. You can learn more about the invoice portal in the [invoice portal documentation](/invoicing/invoice-portal). It's accessible directly on the invoice detail page by clicking the \`Invoice portal preview' button. To test emails manually, ensure that you have a `reply-to` email address set in your account's [invoice settings](https://app.withorb.com/settings?tab=invoices) page. You can leave the "Send email notifications for all Orb invoices" off if you'd like to just test them one-off. Navigate back to your issued invoice, and click "Email Invoice" in the dropdown in the top-right action menu. This will open a confirmation dialog, and if you confirm, an email will be sent to the customer's email address. The email sent will reflect the latest state of the invoice, whether that's issued, paid, or has a payment failure. The email will link directly to the invoice portal and will include, as an attachment a generated invoice PDF. We recommend you confirm that the `reply-to` email is set to your desired email address, so that any questions or concerns from your customers can be directed to the right place. If you navigate back to the Invoice settings page and update the toggle settings to automatically send emails, Orb will send an email whenever a new invoice is issued. This will only send emails for new invoice events, so you will need to set up a subscription to issue another invoice to see that the email is automatically delivered. With emails configured, we can now set up payments so that customers can pay the invoice when they receive an email at the end of the billing period. ## Configuring and testing payments Orb supports Adyen and Stripe as payment providers, and you can fully test the payment flow in test mode with your payment provider's test mode configuration. The walkthrough below uses Stripe; for Adyen, see the [Adyen integration guide](/integrations-and-exports/adyen). After ensuring you are in test mode via the toggle at the top of the page (you should see a green banner), go to the [integrations tab](https://app.withorb.com/settings?tab=payment_gateways) on the Settings page and click "Connect with Stripe" to enter the Stripe Connect OAuth flow to add your test mode account. Note that you will need to grant Stripe permission to connect your account. Once Stripe is connected to your Orb account, you can connect a Stripe customer to your Orb customer by editing the customer details, and picking from an auto-populated dropdown of Stripe customers. Note that the 'Invoice Method' is still Orb Invoicing, signifying that Orb is the source of truth for the invoice and payment status, but that Orb will use the Stripe customer's default payment method to collect payment on the invoice. For details on how Orb determines which payment method to charge, see [Payment method selection](/invoicing/payments#payment-method-selection). ### Creating a test payment method There are a few ways you can trigger payment on an invoice: 1. Auto-collection, displayed above in customer details, will control if newly issued invoices will try to collect payment via the connected Stripe customer on the invoice’s due date. This is fully managed by Orb and is most common for self-serve customers. For more details on payment retries and how Orb handles failed auto-collection, see the [payment behavior documentation](/invoicing/payments). 2. If auto-collection is not configured but invoice emails are configured, then Orb will issue the invoice and allow the customer to pay directly via the invoice portal. This is common for enterprise customers who may have a dedicated billing contact and pay after inspecting the invoice's charges. 3. You can manually trigger collection of an invoice via the internal Invoice detail page by clicking the "Charge now" button on an invoice with a payment gateway connected. ### Testing manual payment We'll test the third option, manual payment, to ensure that the payment flow is working as expected. Navigate again to the issued internal invoice page. Below the amount due on the left side of the invoice should be a large `Charge now` button. Once the invoice is confirmed and processed, you'll see new timeline events on the invoice detail page confirming the payment method which was used and indicating when the payment was collected. The invoice status will be updated to paid. When an invoice is successfully paid, Orb generates a payment receipt PDF. The receipt is included in the payment success email sent to your customer. ## Manual invoice review Once you've tested your invoicing flow in test mode, you're ready to start using invoicing for real customers in live mode. However, you might want to manually review and adjust invoices at first before sending them to customers to ensure everything is set up correctly. We'll turn on manual invoice review in our test mode account first to understand the workflow. You can find this setting in the [Invoice settings tab](https://app.withorb.com/settings?tab=invoices). Now, let's create another customer with the same subscription configuration as the one we [set up before](/quickstart/subscriptions). If you look at that customer's invoice history, you'll see that instead of having an issued invoice, this customer will instead have an invoice with a status of "Action needed". If we click on that invoice, we'll see a banner telling us that this invoice needs to be issued manually. Using this setting gives you much more control over your invoices — now, you have as much time as you need to review and edit invoices before manually issuing them and triggering customer-visible side effects like emails. You can also use the "Action needed" filter on the invoice list page to see all the invoices that are ready to be manually issued and issue many (or all of them) at once. Using manual invoice review can be great as a temporary way to build confidence in your invoicing setup with real customer data before eventually switching over to automatically issuing invoices, or as a permanent way to handle manual steps in the invoicing process (like adjusting invoicing manually for enterprise customers) or spot checking invoices before issuing. ## Additional invoicing configuration For more information about the invoice portal, configuring automated tax collection, adjusting invoices, and customizing the invoice structure, visit the Orb documentation on [Invoicing](/invoicing/introduction). # Create a billable metric Source: https://docs.withorb.com/quickstart/metrics Now that we have usage events in Orb, the next step is to decide how to use them. A billable metric defines an aggregation over usage events — it's a query definition that Orb maintains for the relevant customers as new events stream in. In more technical terms, Orb maintains a materialized aggregate over your event datastream. **Why query-based metrics matter:** Because metrics are queries (not pre-aggregated counters), you can create a new metric today and Orb instantly calculates it across all historical usage—no re-ingestion required. Product teams can experiment with different billing models, Finance can create retroactive metrics for analysis, and Engineering never needs to rebuild pipelines when pricing strategy changes. ## Simple count To get started, let’s create a billable metric that just counts all the events you’ve ingested. 1. Navigate to the [metric creation page](https://app.withorb.com/metrics/new) by clicking "Billable Metrics" in the left sidebar and then "+ New metric". 2. Select "Simple event filters" 3. Update the `event_name` filter to "matches any value". 4. Click "Preview" to see a sample of usage events — here, you’ll see the events you’ve ingested into your account. 5. Click "Next" and ensure the calculation type is set to "Count" 6. Click "Next", and enter "Count all events" for the name. 7. Under "Item", fill in "Count" then click "Publish". Simple Count Although we aren't going to do this yet, when you associate a Price to this metric (which will determine how to charge for the constantly updating aggregate value), you'll assign a price cadence. The cadence determines the time period over which this metric is evaluated (e.g. "Monthly" means we evaluate the metric over the last month of usage events every month), and also determines how often this price will charge for the duration of the subscription. The "Price model" determines how Orb turns the output of the billable metric into an amount to charge the customer. ## Refining your metric We‘ve now created a billable metric, so now let’s start tuning the attributes of the metric. With these attributes, you’ll be able to refine your metric to meet the needs of your business. A billable metric is composed of **filters** and an **analysis** function. Filters enable you to only consider the events relevant to your metric. The analysis determines how Orb turns all of the filtered events into a single number. The "Count all events" metric didn’t filter out any events and the analysis was just a count. Let’s create a new metric that filters to a single event name and counts the distinct values of an event property you’ve ingested: 1. Navigate to the metric creation page under "+ New Metric" > "Simple event filters". 2. Update the `event_name` filter to match a single event name. If you don’t remember which events you’ve ingested, feel free to preview with a "matches any" filter and pick one. 3. Once selected, hit "Next" to proceed to the next page. 4. Update the "Calculation" to "Unique". 5. Set "Over property" to a property you’ve ingested where there are multiple distinct values. 6. The "Sample events" pane will automatically update when you set this property. 7. Click "Next", and name the metric "Count distinct". 8. Under "Item", fill in "Count distinct" then click "Publish". This metric, unlike "Count all events", depends on the values in the usage events you’ve ingested. Usage events that don’t match the `event_name` you selected will not be considered in the analysis. The output of the analysis will depend on the values of the property you selected. Note that you’ll never need to explicitly include a filter for the customer or time range, because Orb will always include those filters when evaluating a metric in the relevant context. For example, based on the plan in which the metric is used, the same billable metric may be evaluated over a monthly or quarterly time range. Now that we’ve gone over the basics of metric creation, you can create a metric to support your business’s billing needs. You can create multiple metrics with different analyses over the same event stream, enabling you to change the way you charge without ingesting new events. ## Advanced metrics ### Computed properties If you want to aggregate over a transformation of properties in your usage events, Orb supports computed properties. When defining the aggregation for your metric, instead of selecting an event property, select "Computed property…" at the top of the list. This will open an editor where you can define the transformation. For example, if you provided file storage as part of your offering and wanted to charge for megabytes downloaded, but your event property is `bytes_downloaded`, you could use a computed property: `bytes_downloaded / 1000000`. Conditional expressions are also supported, so if you wanted to charge double for fast downloads (denoted by `download_speed: fast` in the event properties), you might configure the following computed property: Preview pricing ### Custom SQL Although the basic Orb interface allows you to configure metrics with a series of conditional statements and some preset analysis functions, you may want to define a metric that is more complex. Just as with the basic definitions, Orb automatically parses the SQL you provide and appends the filters for a specific customer and timeframe when evaluating the metric in the context of a subscription. Orb’s web application allows you to switch to SQL, exposing the underlying custom SQL editor. You can do this by clicking the "Convert to SQL" button during the data aggregation step or by selecting a "Custom SQL query" at the beginning of the metric creation flow. In the SQL editor, you can define a query over the table `events`. For example, counting all events would look like: ``` SELECT COUNT(1) FROM events ``` You also have access to all of the properties from the usage events you’ve ingested. You can filter and aggregate over them as if they were columns on the `events` table. For example, if a file storage company wanted to charge for the number of distinct file paths downloaded more than 100 times, they could use the following metric: ``` SELECT COUNT(1) FROM ( SELECT COUNT(1) as c FROM events WHERE event_name = 'download' GROUP BY file_path ) WHERE c > 100 ``` Downstream of your metric definition, Orb supports all billing features, even if your metric contains complex aggregations and subqueries. You’ll get the same robust pricing functionality and usage visualizations as you’d get with simple metrics. Congratulations! You’ve successfully set up billable metrics and are able to create new metrics as your business needs evolve. You’re now ready to configure some pricing that ties to your metric. # Configure your pricing Source: https://docs.withorb.com/quickstart/pricing Let's start tying our data to billing in Orb! We'll assume you've already set up the very simple count metric as detailed in [the prior guide](/quickstart/metrics). In this guide, we'll set up a couple different plans to illustrate the basics of the plan creation flow in Orb. **Why versioned pricing matters:** Orb treats pricing configurations as versioned objects. Product and Finance teams can test new pricing models using [Simulations](/simulations/introduction) against real usage data before deploying. When you migrate customers to new pricing, Orb's diff engine ensures consistency across thousands of subscriptions atomically. If needed, pricing changes can be rolled back because billing is always a deterministic query. ## Setting up your first plan A plan is a collection of pricing information that describes how you want to bill your customers for your product. Often times, a plan corresponds to what you've listed on your pricing page, but it can also be used to model enterprise agreements. To get started, let’s create a simple plan that simply bills your customer monthly at a rate of \$3.00 per event, using the "Count all events" billable metric we created in the [metrics guide](/quickstart/metrics). 1. Navigate to the plan creation page by clicking "Plans" in the left sidebar, and then clicking "+ New Plan" in the top right. 2. Click "+ Add usage-based price", which will add a new price configuration card to the screen to the page. 3. Select the "Count all events" billable metric and specify the "Per unit" rate to be 3.00 USD. 4. Click "Next" and name your plan "Basic" and click "Publish" You should see the newly published plan in the plans tab of the Orb dashboard. If you'd like to jump ahead to testing this plan, feel free to skip to the [subscriptions guide](/quickstart/subscriptions) to see it in action. ## Expanding the pricing model The "Basic" plan is a little expensive, and it doesn't always make sense to be billing \$3.00 for every single event we ingest for our customers. Let's suppose we've also added multi-region support in our product, and we want to change the price per region because some regions cost much more to maintain than others. To handle this, we’ve added a `region` property to the events we send in to Orb, and we’ll use this to model our new pricing scheme. To ingest a few new events specifying a `region` property, you can use a very similar command to the one we used in the [ingestion guide](/quickstart/ingest), but with a few changes. As before, fill in the `TIMESTAMP` and `TOKEN` fields with the appropriate values. ```bash theme={null} curl --location --request POST 'https://api.withorb.com/v1/ingest?debug=true' \ # highlight-next-line --header 'Authorization: Bearer ' \ --header 'Content-Type: application/json' \ --data-raw '{ "events": [ { "idempotency_key": "create-plan-guide-1", "external_customer_id": "demo_customer", "event_name": "function_compute", "properties": { "app_id": "DO1X6mLzDPa7", "execution_ms": 55737, "function_id": "hLvenfMutA5j", "provisioned_memory_gb": 8, # highlight-next-line "region": "us-east-1" }, # highlight-next-line "timestamp": "" }, { "idempotency_key": "create-plan-guide-2", "external_customer_id": "demo_customer", "event_name": "function_compute", "properties": { "app_id": "aMwoQ3ldnJXA", "execution_ms": 23647, "function_id": "g6OidvuWCvT8", "provisioned_memory_gb": 8, # highlight-next-line "region": "us-west-1" }, # highlight-next-line "timestamp": "" } ]}' ``` We’ll want to offer our customers on this plan a 15% discount on event ingestion on the following per-region list rates: | Region | Price per event | | --------- | --------------- | | us-east-1 | \$0.75 | | us-west-1 | \$0.85 | | us-west-2 | \$0.90 | | eu-west-1 | \$0.95 | Note here that we're handling the addition of a new region to our product with a catch-all rate instead of having to make changes to this plan in the future. In Orb, this pricing scheme can be represented as a **Dimensional Price Group**, which allows us to create a price table of the various cost drivers (dimensions) without the cumbersome task of defining multiple metrics. You can think of a dimensional price group as applying a `GROUP BY` operator **across one or multiple dimensions** to the billable metric. In this example, we’ll add one grouping, "region" and we’ll set the price per dimension to the values mentioned above alongside the discount. We’d also like to start charging a \$1000 platform fee that is paid in-advance, which means our customers will pay for this fee for the upcoming month in every cycle. In Orb, we call this a fixed fee, which can be configured to bill in-advance or in-arrears. Although we're configuring a specific price point here in the plan definition, Orb allows adding overrides for a customer when creating a subscription. This allows you to offer discounts or special terms to specific customers while still maintaining a single plan definition and keeping your product catalog simple. Let's go ahead and model this in our Orb test mode environment. As before, navigate to the plan creation page and take the following steps: 1. Click "+ Add dimensional price group" on the top right to add a new price configuration card. Select the "Count all events" billable metric. Although we're going to be reflecting our new price model, note that we can still reuse the same billable metric for this plan. 2. We should then specify "region" for the first dimension by selecting it under "Dimensions". Orb will automatically populate region labels you've sent in the past, but we can also add new ones here. 3. We’ll add the following "regions" as individual dimension values: * us-east-1 * us-west-1 * us-west-2 * eu-west-1 4. For each one of these dimensions, we provide the flexibility for each "region" to specify the pricing model, billing cycle, price adjustment, and discounts. For example: we could choose to give a region a more aggressive discount. However, for this example, we will just apply the same discount across all "regions" and select the "Unit Pricing" price model since we want to charge on the quantity of events. 1. First, add pricing for across each "region" 2. Second, add the 15% discount across each "region". Check "Add discounts" from the "Price adjustments" section of this configuration screen to do so 5. Add the platform fee by clicking the dropdown arrow on the top right and selecting "Add fixed-fee price". Add an item named "Platform access fee" with a per-unit price of \$1000.00 set to recur annually. 6. Click "Next" and name your plan "Basic (Regional)" and click "Publish". Image ## Additional pricing models Dimensional Price Groups and unit pricing are just two basic models that Orb supports out of the box. For a complete tour of the pricing levers available in the platform, check out the full guide on [managing your product catalog](/product-catalog/build-catalog). For more details on dimensional pricing concepts and advanced configuration, see the [dimensional pricing guide](/product-catalog/dimensional-pricing). Let's start using these plans to setup subscriptions for our customers! # Manage subscriptions Source: https://docs.withorb.com/quickstart/subscriptions **Why timeline-based subscriptions matter:** Subscriptions in Orb are modeled as timelines—every change is reversible and precisely timestamped. Sales can backdate enterprise pricing to contract effective dates. Product can test subscription migrations with previews before committing. Finance maintains complete audit trails showing exactly how pricing evolved over time. This is only possible because Orb's diff engine recalculates billing deterministically—never as side effects. ## Creating a new customer In the quickstart so far, you've already [ingested some events](/quickstart/ingest) into Orb and [created a plan](/quickstart/pricing). Now, let's create a customer, so we can subscribe them to our plan. You'll notice that in the events we've ingested so far, we've been tagging them with `external_customer_id = demo_customer`. This `external_customer_id` is effectively an alias for an Orb customer that doesn't exist yet. Let's go ahead and create the corresponding customer through the Orb UI now. In your Orb test mode environment, navigate to the [Customer list](https://app.withorb.com/customers) and click the action to add a new customer. Fill in the form with some sample customer information, and set the `external_customer_id` to `demo_customer`. Keep the default Invoice method (we'll revisit what this means in the next guide) and set the time zone to UTC. ## Provisioning the subscription From the customer page for Bruce Wayne Enterprises, let's click "Add subscription", which will send us into the creation flow. Select the "Basic (Regional)" plan we created in the [plans](/quickstart/pricing) guide for this example. Select a start date for the subscription of today to reflect that their subscription should start immediately. Let's also keep the default selection of "Align billing with the 1st of the month" which means that every billing period boundary other than the first will fall on the 1st. This is a common choice for SaaS businesses, but you can also choose to align billing with the start date of the subscription so you can bill on every 22nd of the month if the subscription starts on the 22nd. Aligning billing to the first of the month will cause any in-advance fees to be prorated according to the length of the resulting first period. For example, if you're provisioning a subscription on the 15th of the month, the first period will be 15 days long, and you'll charge your customer approximately half of the in-advance fixed fee on their first invoice. While this customer is subscribed to our base plan, we’ve negotiated some custom terms with them, including a change to the amount of their in-advance fixed fee. Click the "Override” button next to the accompanying price and change the value from \$1,000 to \$750. This is where you'll specify any custom negotiated discounts, minimums, and other changes to the price for this customer. We can click next to continue forward to the Review step. This page gives us an overview of the plan to which we’re going to subscribe this customer, along with any overrides to particular plan prices. Importantly, it also provides a preview of the first invoice we'll send this customer. Depending on when you're creating your subscription, your total will vary according to the prorated amount of the in-advance fixed fee. Proceed to create the subscription with the "Create” button! Since this subscription has an in-advance fixed fee, you'll see an immediate invoice generated for this customer. Once you're back on the customer page, you should see the newly active subscription. Note that by default, the Orb dashboard creates the subscription as of the *beginning* of the current day, so you may already see some usage from previously ingested events. In the Invoices section further down the page, we can see two invoices: one that was "issued” from the in-advance fee and the one that is in "draft” and set to be issued on the 1st of the following month, as configured. Click to view the already-issued invoice in this table. On this invoice page, we can see that the initial invoice is charging the customer for the in-advance fixed fee. Since the billing alignment is for the first and our subscription began on the 10th, the service period we are charging this customer for is from the 10th to the 1st of the next month. Therefore, the fee is prorated for the shortened service period. Let’s go back and view the "draft” invoice. We can also view the draft invoice from the subscription card at the top by clicking on the date under "Next invoice”. This invoice, with an invoice number suffixed with `DRAFT`, is for the *current* billing period, and contains both the in-arrears usage charges and the in-advance fixed fee for the following month. Note that it's scheduled to issue on the first of the following month. This draft invoice will automatically be updated as new usage is ingested into Orb. In the next step of this quickstart, we'll dive deeper into how we can prepare these invoices for our customers, and start to charge them through a payments integration. # Month-end close Source: https://docs.withorb.com/revenue-reporting/month-end-close A step-by-step guide for finance teams to close accounting periods in Orb This guide walks through the month-end close process in Orb, from pre-close preparation through final reconciliation. ## Pre-close checklist Before closing an accounting period, verify the following: ### 1. All invoices are issued Draft invoices represent unbilled revenue. Before closing: * Review draft invoices in the [Invoices](https://app.withorb.com/invoices) page filtered by status * Determine if any drafts should be issued or voided * For usage-based invoices, confirm the grace period has elapsed (default: 12 hours after period end) Invoices issued after you close an accounting period will have their revenue recognized on the **first day of the next open period** as a catch-up adjustment. This is expected behavior that maintains closed period integrity. ### 2. Credits are reconciled Review prepaid credit activity for the closing period: * Check for credits expiring during the period (recognized as revenue on expiration date) * Verify credit consumption matches expected usage patterns * Review any credit grants or adjustments made during the period ### 3. Subscription changes are complete Backdated subscription changes affect revenue recognition: * Review any mid-period cancellations (generate credit notes for in-advance fees) * Confirm plan changes and upgrades are reflected in invoices * Check for pending subscription changes that should be applied ## Closing an accounting period ### Step 1: Navigate to Reports settings Go to **Settings > Reports** in the Orb dashboard. You'll see a list of accounting periods with their current status. ### Step 2: Close the period Click **Close** on the accounting period you want to finalize. Closing a period also closes all prior open periods. You cannot have gaps between closed periods. ### Step 3: Wait for lock confirmation After clicking Close, the period enters a **lock pending** state while Orb's systems process any in-flight data. This typically takes a few minutes. Once complete, the period shows as **Closed**. ## Post-close reports After closing, run the following reports to verify your numbers: ### Recognized revenue report This report shows revenue recognized during the closed period. Navigate to **Reports > Recognized Revenue** and filter to the closed period. Key things to verify: * Total recognized revenue matches your expectations * Revenue is attributed to the correct service periods * No unexpected catch-up adjustments from prior periods ### Billings report This report shows invoiced amounts during the closed period. Navigate to **Reports > Billings** and filter to the closed period. Compare against: * Total invoices issued during the period * Expected recurring revenue from active subscriptions * Any one-time charges or adjustments ### Deferred revenue report This report shows your liability—revenue billed but not yet recognized. Common sources of deferred revenue: * Unused prepaid credits (based on cost basis) * In-advance fixed fees not yet earned * Annual subscriptions billed upfront ### Unbilled revenue report This report shows revenue recognized but not yet invoiced. Common sources of unbilled revenue: * Usage-based charges accruing on draft invoices * In-arrears fixed fees during the service period ## Reconciliation checks ### Revenue should converge to billings Over time, recognized revenue equals billed revenue. For any completed subscription: ``` Total Recognized Revenue = Total Billed Revenue ``` This may not balance for active subscriptions due to timing differences. ### Deferred + Unbilled = Outstanding obligations At any point in time: ``` Deferred Revenue = Billings - Recognized Revenue (when billings > recognized) Unbilled Revenue = Recognized Revenue - Billings (when recognized > billings) ``` A customer can have both deferred and unbilled revenue simultaneously on different line items. ### Period-over-period consistency Compare your closed period against prior periods: * Total recognized revenue trend * Deferred revenue balance changes * New vs. recurring customer revenue mix ## Reopening a closed period If you need to make corrections to a closed period: 1. Go to **Settings > Reports** 2. Click **Open** on the closed period Reopening a period also reopens all following periods. This ensures no gaps between open and closed periods. After making corrections: * Re-run the reports to verify changes * Close the period again when ready ## Handling backdated actions When billing activity occurs for a closed period, Orb handles it automatically: | Action | Revenue impact | Billings impact | | ------------------------------------------------- | ------------------------------------------- | ------------------------------------------------------- | | Invoice issued for closed period | Recognized on first day of next open period | Recorded on first day of next open period | | Credit note for closed period | Adjustment on first day of next open period | Adjustment on first day of next open period | | Subscription cancelled backdated to closed period | Adjustment on first day of next open period | Credit note adjustment on first day of next open period | This "catch-up" approach ensures closed periods remain immutable while still capturing all billing activity. ## Data freshness Report data in Orb is refreshed continuously. After closing a period: * **Invoice data**: Available within 20 minutes of issuance * **Usage-based revenue**: Available after grace period ends * **Credit consumption**: Updated in real-time as usage occurs For the most up-to-date information, check the "Last updated" timestamp on any report. ## Exporting closed period data For integration with external accounting systems: ### Via data exports Configure [data exports](/data-exports/introduction) to sync billing data to your data warehouse. The `Daily Line Item Revenue Event` resource includes `lock_adjusted_timestamp` for accounting period compliance. ### Via accounting sync Use the [NetSuite](/integrations-and-exports/netsuite) or [QuickBooks](/integrations-and-exports/quickbooks) integration to sync invoices directly to your ERP. ### Via CSV export Export individual reports to CSV from the Reports page for manual reconciliation. ## Troubleshooting ### Period won't close If a period remains in "lock pending" status for an extended time, contact Orb support. This may indicate in-flight data that needs to complete processing. ### Revenue doesn't match expectations 1. Check for catch-up adjustments from prior period activity 2. Verify all expected invoices were issued 3. Review credit consumption and expiration timing 4. Confirm subscription changes were applied correctly ### Deferred revenue balance seems wrong 1. Verify prepaid credit cost basis is set correctly 2. Check for in-advance fees that should be recognized 3. Review credit expiration dates ### Unbilled revenue is unexpectedly high 1. Check for draft invoices that should be issued 2. Review usage-based charges still accruing 3. Verify grace period has elapsed for usage invoices # Reporting configurations Source: https://docs.withorb.com/revenue-reporting/reporting-pivots ## Reporting pivots Docspivots Pn Orb’s revenue recognition dataset is meant to be as granular as possible, allowing you to split data by the following axes: | Pivot dimension | Example use case | | ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Billable Metric | Evaluating how revenue differs across different implementations of a single conceptual measurement to understand which one is most effective. | | Block (prepaid credits) | Tracking the consumption and expiration of prepaid credit blocks to ensure accurate revenue recognition and forecasting. | | Credit note | Understanding contra revenue lost to things like refunds, concessions, subscription changes, etc. | | Customer | A finance team wants to analyze revenue performance per customer to identify high-value clients and potential churn risks. Operationalized by tracking the revenue generated by customer A over the last quarter. | | Invoice | Auditing the revenue recognized from Invoice #12345 to verify it aligns with the usage and services provided. | | Item | Assessing the revenue contribution of "file processing services" versus "platform access fees". | | Plan | Comparing the revenue from the Basic Monthly plan versus the Pro Annual plan to determine which plan drives more consistent revenue. | | Price | Assessing the revenue contribution of "file processing services" versus "platform access fees", or previous versions of the same price. | | Subscription plan | Comparing the revenue attributed to a plan a customer is subscribed to, inclusive of any subscription modifications like add-ons and overrides. | Notably, Orb supports pivoting on multiple axes at once. For example, you might want to power the following use cases: * Customer + Plan: Identify which plans are most popular among various customer segments. For instance, analyze revenue from Customer A across both the Basic Monthly and Pro Annual plans to determine customer preferences. * Plan + Item: Determine the contribution of different items within each plan. For example, evaluate the revenue from file processing versus platform fees within the Basic Monthly plan to optimize plan offerings. * Item + Block: Monitor the usage of prepaid credits against specific items. Track how many prepaid credits were used for file processing services versus compute fees. * Invoice + Billable Metric + Block: Verify that invoicing, pricing, and credit block usage are aligned. Ensure that the pricing applied on Invoice #6789 accurately reflects the consumption of prepaid credits and any discounts given. ## Additional display options Recognized revenue pivot with display options From the display options dropdown in the top right of the Recognized Revenue and Billings reports, configure: 1. View aggregation: rollup data monthly, or daily for shorter time horizons 2. Display properties: include additional metadata for richer reporting in one place 3. Insights: calculate % change from the prior period # Recognition methodology Source: https://docs.withorb.com/revenue-reporting/revenue-recognition Orb’s reporting functionality is designed to help your team simplify your month close process with revenue reports (such as for recognized and deferred revenue) and drive your receivables activities. Orb’s revenue reporting is built with the following principles in mind: 1. **Completeness**: It must be possible to account for all revenue that the billing engine produces. In other words, all features supported by the Orb billing engine will have corresponding revenue accounting treatment. 2. **Consistency**: The way that Orb recognizes revenue is always consistent with how invoices are produced. For example, the proration methodology between billing and revenue recognition does not diverge. 3. **Auditability**: Whether it’s through the Orb API, data exports, or in-product drill down and split views, Orb’s product is committed to providing the lineage of revenue from a summary entry → invoice → raw product usage data. ## Recognized revenue methodology Orb automatically calculates recognized revenue in a way that accords with ASC606 guidelines. Orb’s reporting module handles the full sophistication of Orb’s billing engine, including support for a broad range of usage-based models, prepaid commitments, multiple cadences, and multiple currencies. This section outlines the methodology in the abstract, and the following section covers some illustrative examples. For businesses with a usage-based component to their pricing, calculating recognized revenue can be challenging, as it requires understanding exactly when usage occurs. This can be particularly difficult when invoicing and accounting periods do not align, or for cases like credit drawdowns where invoices may not contain the required information for recognition. An important principle is that Orb guarantees that your recognized revenue always converges to your billed revenue over time. In other words, the amount of revenue that is recognized up to a given date should be consistent with the billing that results in all relevant subscriptions ending on that date. This ensures deferred and unbilled revenue balances reach zero for a customer once all their subscriptions are ended and their prepaid credits are exhausted or expired. Orb’s billing engine supports both usage-based fees (e.g. compute, storage, or API requests) as well as fixed fees that are not tied to event reporting (e.g. support fee or platform fee). Note that per standard accounting guidelines, revenue numbers always exclude tax and the payment status of an individual invoice is not factored into accounting treatment. **Usage-based fees** Revenue is recognized for usage as it occurs on a daily basis, regardless of the associated line item’s service period. Each usage event is reported to Orb with a timestamp property, which determines the time that the event happened. For billing, invoicing, and reporting purposes, this determines the time attribution of the corresponding usage. Note that this recognition behavior is maintained whether or not the usage-based charge leads to an accrual on the invoice or whether it causes drawdown of existing prepaid credits. **Prepaid credits** Prepaid credits are a way for your customer to commit to a certain amount of usage and draw it down through their subscription over time. Recognition for credits is always based on their cost basis, which represents the unit price at which they were purchased. Credits are added to your customer in ‘blocks’, and each block can have a separate cost basis. For example, free trial credits may have a cost-basis of \$0 and hence no revenue would be recognized by their consumption. An enterprise customer might commit to \$1.6M of custom platform credits over the year at the cost of \$800K, determining a cost basis of \$0.50/unit. Prepaid credits typically have an effective date as well as an expiration date. Orb adheres to the following recognition rules: * **At effective date**. No revenue is automatically recognized when the prepaid credits become effective. At this point, all of the revenue should be considered deferred revenue because no service obligation has been met. * **When used.** As stated above, revenue is recognized on a daily basis when usage occurs, derived from how many credits are used in that day based on incurred usage. * **At expiration.** When a credit block expires, its remaining balance is recognized on the day following the stated expiration date, by default. To modify this methodology, reach out to Orb. Note that prepaid credits can either be in a custom currency (e.g. "custom credits unit") or in a real-world monetary currency (e.g. USD). In either case, the cost basis is used to translate the credit burndown amount to the revenue that should be recognized. **Allocations and cost basis** Allocations (recurring credits added automatically as part of a subscription) can be configured with either a zero or non-zero cost basis: * **Zero cost basis allocations**: These represent free inclusions in a plan and do not generate revenue. No revenue is recognized when these credits are consumed, as they have no associated cost. This is common for trial credits or plan-included allowances. * **Non-zero cost basis allocations**: These represent recurring commitments where customers are charged for the allocated credits. Revenue is recognized as the credits are consumed, using the same burndown methodology described above. For example, if a customer receives 5,000 credits per month at a cost basis of \$0.25/credit, the \$1,250 charge is initially treated as deferred revenue and recognized as the credits are used. In some cases, it might be preferable to straight-line recognize the revenue from credits based on the time period that they're effective, instead of using the burndown approach described above. This is more common in cases where the service delivery is tied more closely to platform access rather than marginal credit units. Orb provides this behavior by allowing you to combine an in-advance fixed-fee with \$0 cost basis credits (an allocation). With this configuration, credit consumption does not contribute to recognition, and the fixed fee will be straight-lined (see below). **Fixed fees** Orb’s billing engine has support for fixed-fees, such as seats, support, or platform access. Each fixed-fee is associated with a service period, typically corresponding to the start date of the billing period, the cadence, and whether the fee is billed at the end of the period or in advance. By default, Orb performs proration on fixed fees, meaning that any partial period will result in a partial charge on the invoice. In order to calculate recognized revenue, Orb performs straight line recognition, recognizing revenue on a daily basis. For example, for an up-front platform fee of \$960 in a 30-day month, Orb will recognize \$960 ÷ 30 = \$32 each day. Note that this is true whether this fee is charged in-advance of the month or in-arrears; in either case, the same prorated amount will be recognized each day. **Subscription adjustments (minimums, discounts, maximums)** It’s common in usage-based businesses to have significantly more sophisticated adjustment mechanics, such as enforcing a minimum or a maximum on a given usage-based line item. In Orb, an adjustment like this can also apply to multiple items at once, such as a minimum that applies for a whole invoice consisting of different usage-based charges. Revenue is recognized in the following way: 1. Minimum amounts are recognized on a daily basis (similar to fixed-fees). The outstanding minimum amount to be recognized is distributed evenly across all relevant line items on the impacted invoice. 2. Percent-based discounts reduce the recognized amount of their underlying prices by the percent. No proration logic is necessary. 3. Amount-based discounts reduce the recognized amount of their underlying prices on an accrued daily basis, up to the total discount amount. **Automatically created credit notes** A credit note can be created on an issued or paid invoice, and is automatically created by the billing engine on invoices containing in-advance fees that are no longer expected based on the state of the subscription. For example, a mid-period cancellation involving an in-advance fee will result in a reduced, prorated expected payment and will require a correction in the form of a credit note on the previously issued invoice. In the case that a credit note is generated automatically in order to account for the shorter period, all revenue past the cancellation date is reversed and no longer eligible to be recognized. ## Actions in the past and accounting period locks It’s common to take backdated actions in Orb, such as issuing one-off invoices, creating new subscriptions, editing a plan, or cancelling a subscription in the past. When a backdated action results in a new invoice being created, Orb automatically captures usage that’s been ingested for that invoice. How that revenue is recorded in Orb depends on the lock posture of the monthly accounting periods in Orb. Accounting period locks allow you to control when and how revenue is recognized. Closing an accounting period ensures that there will be no subsequent changes to that period in any Orb Reports due to changes in billing activities. revenue-matrix * When the monthly accounting period is **open**: * **Revenue** from new invoices entered into the period will be recognized in the service period on the newly generated invoice, updating the amounts for those days. * **Billings** from new invoices entered into the period will be recorded on the issuance date of the newly generated invoice, updating the amounts for those days. * **Deferred revenue** calculated for the period will use the updated Revenue and Billings amounts. * When the monthly accounting period is **closed**: * **Revenue** from new invoices entered into the period will be recognized on the **first day of the next open accounting period as a “catch-up” adjustment.** Revenue in previous periods remains unchanged. * **Billings** from new invoices entered into the period will be recorded on the **first day of the next open accounting period as a “catch-up” adjustment.** Revenue in previous periods remains unchanged. * **Deferred revenue** calculated for the period will use the previous Revenue and Billings amounts. Revenue in previous periods remains unchanged. **Setup** close-and-open-periods Account Admins can lock and unlock accounting periods in Orb from the [Reports tab](https://app.withorb.com/settings?tab=reports) in Settings, and update the fiscal year offset for Reports. * Accounting periods in Orb are monthly. * Open and closed periods cannot overlap. All open periods must be sequential and come after closed periods. * Reopening a closed period will also reopen any following closed periods to ensure open periods are after closed periods. * Closing a period will also close any prior open periods. ## Revenue handling of manual actions **Invoice edits** Since Orb features a full invoicing engine, Orb allows you to edit draft invoices through an adjustments process, giving you the flexibility to add discounts or create new line items on an invoice before it has been issued. Invoice edits will be recognized during the line item service period if the accounting period is open. If the accounting period is closed, it will be recognized on the first day of the next open period. **Manual credit notes** When creating a credit note manually (e.g. in the case of an overcharge), the amount that is unrecognized from the original invoice is the amount on the credit note. The date range over which the revenue is recognized is taken from the service period of the original line item, and spread evenly across the service period, if the accounting period is open. ## Deferred revenue Orb provides a deferred revenue report separate from recognized revenue. Conceptually, your deferred revenue is your liability to your customer – it’s a pending service obligation that you are not yet able to recognize because the service hasn’t been delivered. In Orb, your deferred revenue is the amount that has been invoiced (excluding tax) that has not yet been recognized. The most common reasons for deferred revenue are: 1. Unused prepaid credits. Deferred revenue is calculated based on the cost-basis of these credits. Note that only credits created with an associated invoice should be treated as deferred revenue, whether or not the corresponding invoice has been issued. If your customer receives an annual credit allotment but pays for them over the course of a year on a quarterly payment schedule, the deferred revenue should increase for all credits at the beginning of the year. 2. In-advance fixed-fees. For example, an annual platform fee should be recognized on a prorated daily basis over the course of the year. Correspondingly, the deferred revenue account is initially the full fee and then is depleted each day. Deferred revenue can automatically be adjusted as the result of a subscription action, such as a mid-period cancellation where the fixed-fee is credited back to the original invoice. In this case, revenue recognition will no longer take place for that price and there will be no corresponding deferred revenue remaining. ## Billed revenue The Billings report in Orb captures the total amount due from invoices for a given time period. Orb determines which billing period to record the transaction, using the invoice date. When an invoice is voided or a credit note is issued against an invoice, Orb reduces billings by recording negative revenue in the relevant period, in accordance with the posture of the [accounting period lock](https://docs.withorb.com/revenue-reporting/revenue-recognition#actions-in-the-past-and-accounting-period-locks) at the time of the void action, or credit note issuance. ## Unbilled revenue In traditional subscription businesses, it’s common to charge everything up-front, meaning that revenue recognition is conceptually a subset of billed revenue. However, usage-based businesses commonly accrue revenue on ‘draft invoices’, implying that revenue must be recognized before it’s billed. This is your “unbilled” revenue. The most common cases where you should expect unbilled revenue are: 1. Usage-based charges: At any given point in time, your draft invoices in Orb will be accruing usage based on the events sent into the platform. Although these invoices haven’t been issued, Orb is still accruing unbilled revenue. 2. In-arrears fixed-fees: For example, suppose you charge a monthly support fee at the end of each month. Orb will recognize this revenue as the month progresses even though the fee has not been billed. Deferred revenue and unbilled revenue should always be considered on a per-line item basis. In particular, it’s possible for a customer to have both deferred revenue and unbilled revenue (see below) on different fees. To take the simplest case, the current period might have a \$100 in-advance and a \$100 in-arrears fee. At any given point in time, this customer will have both partial deferred revenue (corresponding to the in-advance fee) and recognized, unbilled revenue (corresponding to the in-arrears fee). # Examples Source: https://docs.withorb.com/revenue-reporting/revenue-recognition-examples This section provides examples of how Orb can help you manage revenue recognition for your business. The examples are based on a fictional company, FileCo, that provides a cloud-based file storage service. FileCo has a subscription-based business model, and its customers pay monthly for access to the service. For the purposes of this example, suppose you're FileCo and you're using Orb for billing and revenue recognition. Note that this series of examples makes some assumptions about how FileCo thinks about its service obligations for the sake of illustrating common cases. ## Monthly platform FileCo sells a file processing service, and you currently offer the following monthly plan: **Basic Monthly** (\$10/mo). When a customer signs up for your service on April 1st, Orb will generate an in-advance invoice for \$10. This revenue is billed but it's not yet eligible to be recognized and is instead considered deferred revenue. After each subscription day passes, Orb will recognize approximately \$0.33 for specifically the platform fee, representing the prorated amount of the \$10 corresponding to 1/30th of the service period. At the end of the invoice service period, Orb will have recognized the full \$10 for the platform fee. Suppose now that you find you need to add a usage-based component to your pricing in addition to the platform fee, because of variable costs. Let's add a usage-based component. **Basic Monthly w/overages** (\$10/mo, and the cost of processing each file is \$0.50/file) Suppose a customer processes 800 files over the course of April, leading to \$400 of usage-based charges billed in-arrears on the May 1 invoice. As expected, \$400 of usage-based revenue attributed to file processing will be recognized during the month of April. More specifically, Orb will recognize revenue to the specific day on which usage occurred, so April 2 may have \$100 of recognition on a single day if 200 files were processed on April 2. Note that if you allow subscriptions to be aligned to a date other than the first of the month, Orb will correctly apply the rules to the partial months. For example, subscriptions started on April 10 will recognize 20 days of fixed-fee revenue in the April accounting period, and 10 days in May. Similarly, usage-based recognition would be split between April and May based on the day and month the usage occurred. Let's take an example of a mid-period cancellation. 1. July 1: Orb generates an in-advance invoice for \$10. This \$10 is deferred revenue. 2. Daily Recognition: Each day in July, Orb recognizes approximately \$0.33 (\$10/30 days) as revenue for the base fee. 3. Usage in July: Customer C processes 300 files in July, leading to \$150 (300 files \* \$0.50) in usage-based charges. Here's how Orb's revenue recognition system handles the cancellation: 1. July 15: Customer C cancels the subscription. * Orb generates a credit note for the remaining service period. * Recognized revenue up to July 15 for the base fee: \$0.33 \* 15 = \$4.95. * Remaining deferred revenue for the base fee: \$10 - \$4.95 = \$5.05. * Orb reverses the deferred revenue for the unused period and updates the recognition schedule. 2. The \$150 for usage in July is recognized on the specific days the files were processed. Since the usage occurred before the cancellation, there are no adjustments needed for the usage-based charges. For larger customers, say you want to offer an annual plan, providing a discount in the per-file charge: **Pro Annual (\$500/year)**. The cost of processing each file is \$0.02/file for the first 1,000 each month, and each file thereafter is \$0.01/file. Overages are charged monthly. If a customer signs up for your service on January 1, Orb generates the in-advance invoice for \$500 and starts to recognize roughly \$1.37 each day of the corresponding year. Note that \$1.37 is derived by prorating the annual platform fee to its daily value. If your customer were allowed to cancel their subscription at any time during the annual service period, Orb would create a credit note against the \$500 invoice which would exactly match the portion of the \$500 that hadn't yet been recognized. Each day, Orb will continue to recognize revenue based on the cost associated with each file's usage-based charge. In any given month, a file may lead to more or less revenue depending on whether 1,000 files have been processed in that month – this will be treated accordingly in the revenue recognition results. ## Offering prepaid credits In order to provide more flexibility to your offering, you've started to allow customers to buy "file credits" at \$0.03 per credit as long as the commitment is at least 300 file credits. These credits expire in one year, and can be used at any time within that year. Suppose a customer purchases 8000 credits on March 15, at the standard price of \$240. As soon as these credits become effective, \$240 is recorded as deferred revenue. If 2000 credits are used in the month of March, then \$60 of revenue will be recognized in March. This is calculated by multiplying 2000 by the cost basis of \$0.03/credit. Suppose that the customer does not use the service for any future periods, and the credits expire the following year in March. Orb will not recognize any revenue until the following March, where the remaining \$180 will be recognized. Note that in this simple example, there's exactly one 8,000 credit block all purchased at the same time. Orb will properly handle revenue recognition when there are multiple blocks with separate cost bases, potentially of different currencies, all expiring at different times. Importantly, recognition can happen at any arbitrary date in the month – whether this is due to usage (and so a block was depleted), or expired. ## Up-front implementation fee At FileCo, you're finding that there are often customers that require a large upfront implementation to support their file type for processing. You want to experiment with charging a one-time \$10,000 fee in exchange for a statement of work around implementing their file format. This \$10,000 fee will be recognized as of your customers' subscription start date as a point-in-time fee. # Pricing an AI agent Source: https://docs.withorb.com/self-serve/agent-pricing In this example, we'll set up our pricing model as follows, which requires no upfront commitment: * Each month, your customer receives 100K free tokens. As your customer uses your AI agent, they will consume these tokens in real-time. * Customers must have available tokens in order for the agent to make progress. * Customers can either buy tokens on-demand, or set up an automatic top-up to buy tokens on their behalf when they're depleted. * The cost of on-demand tokens is \$10 per 100K tokens. We'll also set up a higher tier plan ("Premium") that costs \$50/mo upfront, and grants 1M tokens per month. More tokens can still be bought at the same on-demand token price of \$10 per 100K tokens. Self serve agents plan interstitial Free tokens that are granted each month will expire at the end of the month, and on-demand purchased tokens will expire one year after they're granted. Setting expiration dates for tokens and credits is a best practice that prevents unlimited liabilities in your accounting books. Be transparent with customers about when their tokens will expire to maintain trust and avoid surprises. ## Core modeling ### Tracking usage as events You'll send a usage event that tracks the consumption of credits from your agent at every agent checkpoint while completing a task. Decouple product usage from pricing configuration by sending events with raw, real-time usage data instead of rated quantities like dollars or cents. Use Orb's billable metrics and plan definitions to configure your pricing. This decoupling enables pricing evolution without modifying product code. Structure the event as follows: ```json theme={null} { "event_name": "agent_checkpoint", # customer ID in your system "external_customer_id": "customer-18940", "timestamp": "2025-03-09T16:09:53Z", "properties": { "input_tokens_used": 240, "output_tokens_used": 4599, "reasoning_tokens_used": 3021, "model": "claude-3-7-sonnet-20250219", "region": "us-west-2", "hardware": "instance-type-2", "task_id": "5e0f1823-d525-456c-abad-45c917d2d025", "checkpoint": 3 }, # set the idempotency key based on the task_id + checkpoint number "idempotency_key": "5e0f1823-d525-456c-abad-45c917d2d025-3" } ``` ### Constructing your metric For our example metric, we'll charge based on the total token utilization count across all tasks for a given customer, regardless of model or hardware. The metric will be set up as follows: Self serve agent request tokens ### Setting up your pricing model Create a custom pricing unit called `tokens` that users accrue when they pay for monthly subscriptions or make top-up purchases. These tokens are consumed whenever an agent is used. Orb's [prepaid credits feature](/product-catalog/prepurchase) manages this token-based billing model efficiently. To model this pricing in Orb, we'll need two Plans, one that represents the free tier and another that represents the Premium tier. A plan in Orb is a pricing model that you can associate with one or more customers; in our case, each customer will be associated with one of these plans depending on their tier of service. In order to make referring to these canonical plans easier, set the `external_plan_id` of these plans to `free` and `premium` respectively. Similar to the `external_customer_id`, this allows you to refer to these plans in your codebase without having to store Orb's internal plan IDs. The only difference between these plans will be the included allocation and the monthly recurring cost. The free tier includes 100,000 tokens each month and does not have any recurring charges. Our plan needs a single price: a very simple usage based price that tracks the drawdown of tokens in `token` currency. The billable metric tracks request tokens directly, so pricing uses a simple 1:1 unit price. Each request token consumed reduces the token balance by exactly one `token` without any conversion factors. Self serve agent free plan Our Premium plan is similar, but includes a \$50 monthly fee and 1 million tokens each month that do not rollover. Self serve agents pro plan ## Customer initial sign-up Guide your customer through an account creation process and collect basic authentication and profile information. You'll need to store this in your own application database as well as sync it to third party systems. **Stripe action:** Create a Stripe customer corresponding to your customer using the [create customer API](https://docs.stripe.com/api/customers/create) endpoint. Create the Stripe customer regardless of whether this customer has payment details or is on a paid offering. We recommend you do this asynchronously to avoid increased latency or a chance of errors in your signup flow. **Orb action:** When you create the Stripe customer, you'll receive a `customer.created` webhook from Stripe (see [this guide](https://docs.stripe.com/webhooks) for setting up webhooks in Stripe). When a customer has been created in Stripe, create the customer in Orb using Orb's [create customer API](/api-reference/customer/create-customer) endpoint. * Note that this requires the customer's name and email address as required fields. Your customer will have an id in your application – pass this as the `external_customer_id` to this Orb API endpoint, which will allow you to take all customer-related actions in Orb's API directly based on this internal id rather than having to store Orb's created customer ID. * Pass `payment_provider=stripe_charge` and your Stripe customer ID as `payment_provider_id` to this Orb endpoint. This step associates the Orb customer with the Stripe customer, allowing Orb to later pull in payment method information and trigger charges on your behalf. * The Orb customer creation should happen on your backend and be asynchronous (i.e. non-blocking) to your application's sign-up process. This is important to avoid additional latency and error handling in your critical initial sign-up process. Upon creating the customer, also create a `credit_balance_depleted` and `credit_balance_recovered` alert using the [alert creation API](/api-reference/alert/create-customer-alert). Note that the currency of these alerts should be set to token, corresponding to your pricing setup. We'll explain this below, in the section on customer spend controls. ## Collecting payment information As part of your new user onboarding process, your customer will pick whether to continue on the Free plan, or immediately upgrade to the paid Premium offering. Both the Premium offering and the ability to activate top-ups will require collecting payment method information. In the event that the user picks the Premium plan, you're responsible for collecting their payment method information and storing it in Stripe as the customer's default payment method. Orb will trigger payments against this saved payment method, and automatically track changes to the default payment method for future payments. ### Setting up Stripe payment method with `SetupIntent` flows Collect their payment information via Stripe Elements following [this guide](https://docs.stripe.com/payments/save-and-reuse). Although Stripe's [hosted checkout flow](https://docs.stripe.com/payments/checkout) is also an option, we recommend Stripe Elements for their ability to customize and embed the form directly into your experience: * The payment information you're collecting should be usable for future, recurring payments that Orb will trigger on your behalf. * Payment information should be attached to the customer's corresponding Stripe customer object and be set up for future use. * When [creating the `SetupIntent`](https://docs.stripe.com/payments/accept-a-payment-deferred?platform=web\&type=setup\&lang=python#create-intent) (required on the server-side before you confirm the setup process on the client), make sure to associate the intent with the Stripe customer. Set `usage=off_session`. * Use a [Stripe Element with mode `setup`](https://docs.stripe.com/js/elements_object/create_without_intent) – you're just collecting the user's payment method, not collecting immediate payment through this flow. Pass `setupFutureUsage: off_session` as an option to ensure that the appropriate payment methods are displayed to your user. * Collect the billing address details using the [address element](https://docs.stripe.com/elements/address-element) – when you choose to charge tax in the future, this will be a critical piece of information required for each customer * Submit the setup by calling `stripe.confirmSetup` on your client side, passing in the `clientSecret` returned by creating the `SetupIntent`. ### Ensuring Orb has your payment information Ensure that the `SetupIntent` successfully sets the Stripe customer's `invoice_settings.default_payment_method` to the collected payment method. When triggering charges, Orb uses `customer.invoice_settings.default_payment_method`, falls back to `customer.default_source`, and if neither is set, will automatically use the most recently created payment method attached to the customer. Do this by listening to the `setup_intent.succeeded` [webhook](https://docs.stripe.com/api/events/types#event_types-setup_intent.succeeded) and ensuring that the customer's newly attached payment method is set as the default payment method. Once you've updated the Customer's default payment method, Orb will sync that payment method as the preferred payment method for future charges. For complete details on payment method selection logic and best practices, see [Payment method selection](/invoicing/payments#payment-method-selection). **Address synchronization**: In addition to payment method information, Orb can automatically keep customer addresses synchronized with Stripe. When you link an Orb customer to a Stripe customer, Orb will automatically pull the customer's billing and shipping addresses from Stripe and update the corresponding Orb customer record. Additionally, Orb can be configured to automatically sync any address changes via Stripe's `customer.updated` webhook, ensuring that billing and shipping information remains consistent between both platforms without manual intervention. This automatic synchronization helps maintain accurate customer information for tax calculations, invoice delivery, and compliance requirements. For more details on configuring address syncing, see the [Stripe integration documentation](/integrations-and-exports/stripe#customer-address-syncing). ## Configuring payments and invoicing Your customers can expect to receive two types of invoices given our pricing model: A recurring charge for their subscription tier. Free customers will not be invoiced for recurring charges, but Premium customers will receive a \$50 monthly invoice. Charges for credit purchases. These may either be automatically triggered through the top-ups mechanism or through a manual credits purchase through your product dashboard. Configure the following settings in your Orb instance as a baseline: 1. Fill in 'Company Details' – this name and address information will be populated on your invoices. 2. Upload your logo – this is used to brand your invoice PDFs, invoice emails, and Orb invoice links 3. Set your invoices to automatically issue under 'Invoice Control'. 4. Enable payment failure recovery (also known as dunning). Orb will automatically retry payments on a fixed schedule when they fail, and smartly retry failed payments when a payment method is updated. Because our pricing hinges entirely on a prepaid tokens model, we don't expect to ever charge our customers for 'pay as you go overage' at the end of each billing cycle. As a result, free plan invoices will still be generated but automatically marked as paid each month. Similarly, invoices for the Premium plan will include a \$50 fee for the monthly recurring charge, but a \$0 line item for usage charges which will have been otherwise paid. ### One-off credit purchases If your customer wants to purchase more tokens on either plan, you should add and invoice those tokens through Orb. In order to do this, increment credits using the [create ledger entry endpoint](/api-reference/credit/create-ledger-entry-by-external-id), passing in the following parameters: * Set the currency to `token`. * `entry_type` = `increment` signifies an increment in tokens * `amount` represents the additional tokens being bought. For our pricing model, ensure that this is a multiple of 100K. * `per_unit_cost_basis` represents the cost of each token in your invoicing currency, USD. Because the purchase rate of tokens is \$10 per 100K tokens, set this to \$0.0001. * `expiry_date` represents when the tokens will automatically be depleted. Set this to one year from the grant (current) date. * To ensure credits are billed immediately and granted conditional on payment success, set `invoice_settings.auto_collection` to True, `invoice_settings.require_successful_payment` to True, and `invoice_settings.net_terms`to 0. Once a purchase has been triggered through this endpoint, credits will automatically be granted to the customer on payment and be usable for your agent's runs. ## Customer usage visibility Providing real-time customer spend visibility is extremely important. In our agent pricing, users should understand: * Tokens used in the current cycle as well as historically * Previously issued invoices, including those for top-up charges In order to understand charges for the current cycle, use Orb's [upcoming invoice](/api-reference/invoice/fetch-upcoming-invoice) API. The upcoming invoice is the invoice for the current billing cycle, so it will represent how credits have been utilized in the current period. In order to make sure your dashboard is performant and responsive, we recommend using Orb's [`Orb-Cache-Control` header](/api-reference/cached-responses), passing `Orb-Cache-Control: cache` as a directive. Asynchronously, you can rehydrate this data without the header and automatically refresh live usage data. In the upcoming invoice response, filter to the line item that represents your token utilization price and use the `line_items` serialization's [`credits_applied`](/api-reference/invoice/fetch-upcoming-invoice#response-line-items-credits-applied) field to show the number of tokens used in the current billing cycle. In addition, you should list the customer's invoices by using the [list invoices endpoint](/api-reference/invoice/list-invoices) filtered to your customer's `external_customer_id`. With this response, you'll be able to: 1. Extract historical token usage by parsing the `line_items[].credits_applied` field for invoices that correspond to recurring subscription charges. You can identify these invoices by the presence of `invoice_source=subscription`. 2. List all historical invoices in a table format. We recommend that you annotate this table with payment status (the `invoice.status` field) as well as explicitly annotate invoices that were a result of credit purchases (`invoice_source=one_off`). Self serve agents billing usage ## Customer spend controls Self-serve customers should be able to control and manage their spend. We'll provide the following mechanisms: 1. [Credit balance alerts](/usage-alerting/balance-alerts) so that your customers are notified when their token balance reaches a specified threshold. * On customer sign-up, you've already created an alert for balance depletion and balance recovery. * If your user wants to opt-in to an additional alert threshold, use the [create customer alert](/api-reference/alert/create-customer-alert) endpoint on-demand from your server to configure additional alert. The customer threshold should be set in the thresholds object array, and the currency should be set to token. * After having configured an alert, Orb will send your webhook receiver a webhook event when relevant state change happens – use this webhook to trigger an email notification to your customer. Additionally, use the webhook to either disable or enable agent functionality if the balance is depleted or recovered, respectively. 2. [Automatic top-ups](/product-catalog/prepurchase#automatic-top-ups), to ensure that additional tokens can be purchased without intervention when the customer's token balance is low. Top-ups should be opt-in and disabled by default. * If a customer opts in to top-up behavior, configure the top-up for the token currency by using the [create top-up endpoint](/api-reference/credit/create-top-up). Note that this requires a threshold configuration (this is the 'breach' point at which a top-up will be triggered) and an amount. Set the amount to always be 100,000 and configure the threshold according to user preferences. * To ensure that payment for top-ups happens immediately and the grant of tokens is conditional on payment, set `invoice_settings.require_successful_payment` to True, `invoice_settings.auto_collection` to True, and `invoice_settings.net_terms` to 0. * In order to allow the user to manage potential runaway spend, allow configuring a max top-up limit. When a number of top-up triggers is hit, disable the top-up by calling [deactivate](/api-reference/credit/deactivate-top-up-by-external-id) on the top-up. Self serve agents spend management Ensure that you notify your customers when their auto top-up threshold is hit, and consider notifying them prominently that they have additional credits beyond their tier's allotment in the current billing cycle. Self serve agents topup bar ## Upgrades, downgrades, and cancellations There are several subscription management flows you'll want to support between the Free and Premium tier of your app. ### Cancellation of the Premium tier If your customer cancels Premium, execute a [plan change](/api-reference/subscription/schedule-plan-change) operation in Orb, with the free plan as the target plan passed via the `external_plan_id=free` body parameter. Because the user has already paid for their credit allotment for the current month, pass `change_option=end_of_subscription_term`. This will schedule the plan change so that future charges no longer apply. Note that you should not reclaim or expire any existing credits; by default, credit allocations on Premium expire within a month and ad hoc credit purchases should remain until their original expiration date. ### Upgrade from Free to Premium tier If your customer upgrades from Free to Premium, execute a plan change operation in Orb, with the Premium plan as the target plan passed via `external_plan_id=premium` body parameter. Because the user expects an upgrade in service immediately, pass `change_option=immediate`. Furthermore, set the `billing_cycle_alignment=plan_change_date` to ensure that the user is charged \$50 on upgrade. This will transition the subscription to Premium, and automatically issue the \$50 invoice for in-advance charges. In order to show the previewed charges as the result of the upgrade (especially for more complicated cases that involve prorated charges), you can [dry-run](/essentials/dry-run) the plan change when first loading the checkout flow by passing the header `Orb-Dry-Run: true`. In addition, pass `Include-Changed-Resources: true` to [fetch a preview](/essentials/previews) of created invoices in the response. ## Manual operations ### Compensatory or promotional credits In the event that a customer has an issue with their credit balance or you want to one-off provide credits to a customer, you can manually grant additional tokens by using the [create ledger entry endpoint](/api-reference/credit/create-ledger-entry-by-external-id) and use the `description` field to explain the nature of the grant. In this event, you should create the grant with a cost basis of \$0 to avoid any additional charges to the customer. Similarly, you can grant credits directly through the Orb webapp by navigating to the customer's page and clicking the "Adjust credits" button, being sure that you are adjusting credits in the token currency: Self serve agent compensatory credits # Product access with prepaid credits Source: https://docs.withorb.com/self-serve/product-access You can use Orb to both manage prepaid credit balances for your customers and to control access to your product based on those balances. This is useful for companies that want to ensure that customers are only able to access their product if they've paid for it in advance. In this guide, we'll walk through how to integrate Orb's prepaid credit management functionality and alerting features to accurately manage product access. We'll use a model that's a very simple version of a text message API that uses credits: * No messages are sent unless the customer has purchased credits. * Customers must purchase credits through your product's admin dashboard. Each credit costs \$0.01. * The customer can purchase credits in advance, and the credits will be automatically deducted from their balance as messages are sent. * Messages can be used at very high throughput, and the customer needs to be blocked from sending messages if they run out of credits as close to the event as possible. * Sending messages is performance critical, so it's not possible to incur the latency of checking the customer's current balance before each message send takes place. Incurring this latency is acceptable on a very infrequent basis. ## Prerequisites In your integration, ensure that you create an Orb customer corresponding to each of your own customers. A customer always starts with 0 credits in each of their prepaid balances; for the purposes of this example, we'll work in a custom prepaid credits currency called `message_credits`. Upon creating a customer via the [create customer API](/api-reference/customer/create-customer) endpoint: * Create a balance depleted and balance recovered alert [via the API](api-reference/alert/create-customer-alert#create-customer-alert). Ensure that you're creating these alerts in the correct currency, passing the `identifier` of the currency (in this case `message_credits`). For these alert types, the `thresholds` body parameter is not required, since the threshold is implicitly set to 0 and these alerts fire on either side of that threshold. As long as these alert configurations are created, you won't need to store the configuration ID in your data model. * Create a subscription for the customer. Note that subscriptions in Orb do not have to be explicitly marked as prepaid, and the customer does not have to be specially configured to draw down from the prepaid balance. Instead, any price that's part of the subscription that is in the `message_credits` currency will automatically draw down from the `message_credits` prepaid balance. You can mix and match price currencies in a subscription, and Orb will automatically draw down from the appropriate balance. * In your data model, create a nullable timestamp field called `messages_blocked_until`. When this field is a non-null timestamp in the future, the customer is blocked from sending messages. When this field is `null`, the customer is not blocked from sending messages. ## Managing new credit purchases In order to support credit purchasing through your dashboard, your server-side logic should increment credits using the [create ledger entry endpoint](/api-reference/credit/create-ledger-entry-by-external-id), passing in the following parameters: * Set the currency to message\_`credits`. * `entry_type` = `increment` signifies an increment * `amount` represents the additional credits being bought. * `per_unit_cost_basis` represents the cost of each token in your invoicing currency, USD. Set this to \$0.01, since each credit costs \$0.01. * `expiry_date` represents when the `message_credit` will automatically be depleted. Set this to one year from the current grant date. * To ensure credits are billed immediately and granted conditional on payment success, set `invoice_settings.auto_collection` to `True`, `invoice_settings.require_successful_payment` to True, and `invoice_settings.net_terms`to 0. ## Sending messages In your application logic, you need to ensure that customers are only able to send messages if they have enough credits to do so by interpreting the `messages_blocked_until` field. Use the following logic to determine eligibility based on the value of `messages_blocked_until`: * If the field is `null`, your application should allow the customer to send messages. This is the case where the customer has a positive credit balance. * If this field is `MAX_DATE`, your application should block the customer from sending messages. This is the case where the customer has no known credit blocks that may become effective. * If the field is a future timestamp, your application should disallow the customer from sending messages. This is the case where the customer has no positive credit balance, but there is a credit block that will become effective in the future. * If the field is a non-0 timestamp in the past, your application should re-query the customer's current balance using the [current credit balance](https://docs.withorb.com/api-reference/credit/fetch-customer-credit-balance) and make a determination based on the new value of `messages_blocked_until`. This is the case where the customer had no positive credit balance, but a credit block may have become effective so a re-query is required. Note that this check must be performed on every message send, since the customer may have purchased more credits in between messages, the customer may have exhausted their credits, or the passage of time may have caused a credit block to expire or become effective. ## Blocking product access on depletion Orb guarantees the delivery of a [balance depleted alert](https://docs.withorb.com/integrations-and-exports/webhooks#customer-credit-balance-depleted) when there are 0 remaining `message_credit`s. When testing, parse this webhook and confirm that `properties.pricing_unit.name` matches `message_credit`. Simply listening to this webhook is **insufficient**. This is because webhook systems do not provide ordering guarantees, and you need to ensure that the customer is only blocked from sending the message under the appropriate conditions. For example, suppose that a customer has a balance of 10 message credits, sends 10 messages and immediately purchases a new block in quick succession. In this case, the balance depleted alert may be delivered via webhook after the block has been purchased, and the customer will be erroneously blocked from sending messages. Instead, you should always re-query the customer's [current credit balance](https://docs.withorb.com/api-reference/credit/fetch-customer-credit-balance) using the API when you receive a balance depleted webhook. This API is never cached, and will always return the most up to date balance per the usage events that Orb has ingested; it will also reflect any other operations that have been performed on the balance, such as the purchase of new credits or the expiration of existing credits. For this request, be sure to set `include_all_blocks=False` to only query for active blocks at the current point in time. In order to set `messages_blocked_until`, use the following logic: * If the balance is greater than 0, set `messages_blocked_until` to `null`. * If the balance is 0, set `messages_blocked_until` to the earliest future `effective_date` of any of the customer's credit blocks. If the customer has no credit blocks effective in the future, set `messages_blocked_until` to `MAX_DATE`. ## Unblocking product access If your customer purchases more credits through your product dashboard, continue using the [create ledger entry endpoint](/api-reference/credit/create-ledger-entry-by-external-id) as detailed above. It's also important to ensure that new credits are set to be effective as of the **beginning of the current subscription billing period**. You can accomplish this by setting the `effective_date` of the entry to match the `current_billing_period_start_date` of the subscription (you'll need to make sure that you have the latest [subscription](https://docs.withorb.com/api-reference/subscription/fetch-subscription) response). This amounts to 'backdating' the credits to the beginning of the billing period, and ensures that your customer never incurs any overages for the current billing period. Specifically, if there are seconds or minutes of lag between the consumption of credits and your system consuming the webhook, your customer may temporarily accrue a small overage charge until product access is blocked (and more credits are bought). When the customer purchases more credits, the above `effective_date` strategy will ensure no charges remain on the current period invoice. After creating the block, query again for the customer's current balance using the [current credit balance](https://docs.withorb.com/api-reference/credit/fetch-customer-credit-balance) endpoint and use the same exact logic detailed above, setting `messages_blocked_until` to `null`, `MAX_DATE`, or a different future effective date. ## Handling balance recovered webhooks Orb will send a balance recovered webhook when a customer's credit balance is no longer at 0. This may happen when a customer purchases more credits, and handles the case where this action happened outside of the context above (e.g. through a manual intervention by a customer service representative). This will also handle cases like a subscription change where a user upgrades to a subscription that includes a recurring credit allocation, because Orb will automatically create a new credit block and send a "balance recovered" webhook. Similar to the balance depleted webhook, this webhook will not provide ordering guarantees so it's important to not solely depend on this webhook to unblock product access. When you receive this webhook, you should re-query the customer's current balance using the [current credit balance](https://docs.withorb.com/api-reference/credit/fetch-customer-credit-balance). Follow the same logic as above to determine the value of `messages_blocked_until`. ## Automated credit allocations Orb supports the concept of a recurring allocation, which is the concept of a credit block that is automatically granted by the subscription on a recurring basis. This is useful for companies that want to provide their customers with a certain amount of credits each month as part of their subscription. When a customer has an active allocation, future credit blocks are created automatically by the system, and Orb guarantees that the next block will exist while the current one is active. Because of this, the above strategy of setting `messages_blocked_until` to the earliest future effective date will ensure that future allocations are accounted for in our message sending strategy. ## Credit expirations In Orb, each credit block can expire at any time after it is effective. When a credit block expires, you will receive a balance depleted alert. As per the section above, you should re-query the customer's current balance using the [current credit balance](https://docs.withorb.com/api-reference/credit/fetch-customer-credit-balance) API and update `messages_blocked_until` accordingly. If the customer has no remaining blocks after the expiration, `messages_blocked_until` will be set to `MAX_DATE`. ## Overages due to eventual consistency Because Orb's event ingestion system is designed to be eventually consistent and it's not feasible to wait for all events to be ingested before sending a message, it's still possible that a customer may send a very small number of messages before they're blocked after they've exhausted all their message credits. We recommend setting your `conversion_rate` to \$0.00 for the `message_credits` currency, since this will ensure that the customer is not charged for any overages that may occur. In the vast majority of cases, this is an acceptable tradeoff but reach out to Orb if you want to discuss how faster SLAs may bound the potential overage costs. # Trials with prepaid credits Source: https://docs.withorb.com/self-serve/trial-management In this example, we'll set up a 30-day trial that ends when either the time limit expires or the customer runs out of credits, whichever comes first. Customers can upgrade to a paid plan at any time by adding a payment method. The trial works as follows: * Customers receive 1,000 free credits when they sign up. These credits expire after 30 days. * The trial ends when the customer runs out of credits or when 30 days have elapsed, whichever comes first. * Customers can use your product while they have available credits. When credits are depleted, access is blocked. * Customers are not invoiced during the trial. Invoices only occur after they add a payment method and upgrade to the paid plan. * When a customer adds a payment method, they immediately upgrade to a \$20/month Pro plan that includes 20 credits per month. Additional credits can be purchased on-demand or via automatic top-ups. Use separate plans for trial and paid tiers. This provides clear visibility into trial vs paid customers and ensures credits persist through the upgrade process. ## Core approach ### Two-plan model Create two separate plans in Orb: 1. **Trial plan**: No recurring charge, no credit allocation. Acts as a marker for trial customers and defines the trial period (30 days). 2. **Pro plan**: \$20/month recurring charge with a 20-credit monthly allocation. This is the paid tier customers upgrade to. The trial plan does not include a credit allocation. Instead, you grant credits separately using the ledger entry API. This ensures credits persist when customers upgrade from trial to paid. Credits granted via plan allocations expire when you change or cancel the subscription. Credits granted via the ledger entry API persist across plan changes and only expire based on their configured expiration date. Send usage events as customers consume credits and configure your billable metric to draw down from the prepaid credit balance. ## Prerequisites Before starting a customer on a trial, set up the following: ### Credit balance alerts Create two alerts for the customer using the [create alert API](/api-reference/alert/create-customer-alert): 1. **`credit_balance_depleted`**: Fires when the customer's credit balance reaches 0 2. **`credit_balance_recovered`**: Fires when credits are added after depletion Set the currency of these alerts to match your pricing unit (e.g., `credits`). You'll use these webhooks to block and unblock product access. ### Plan creation Create both the trial plan and Pro plan in Orb: **Trial plan configuration:** * No recurring charges * No credit allocations * Set a default subscription duration of 30 days (configure this as the plan's default term) * Set `external_plan_id=trial` for easy reference in your code **Pro plan configuration:** * \$20 monthly recurring charge (in-advance) * 20-credit monthly allocation in `credits` currency * Credits expire at the end of each month (set this on the allocation) * Set `external_plan_id=pro` ## Trial setup ### Subscribe customer to trial plan When a customer signs up, create a subscription to the trial plan: ```python theme={null} subscription = orb_client.subscriptions.create( external_customer_id="customer-18940", external_plan_id="trial", start_date="2025-03-09T00:00:00Z", end_date="2025-04-08T23:59:59Z", # 30 days from start net_terms=0 ) ``` Set `end_date` to 30 days from the start date. This schedules automatic cancellation if the customer doesn't upgrade. ### Grant trial credits After creating the subscription, grant credits using the [create ledger entry](/api-reference/credit/create-ledger-entry-by-external-id) endpoint: ```python theme={null} orb_client.customers.credits.ledger.create_entry_by_external_id( external_customer_id="customer-18940", entry_type="increment", amount=1000, currency="credits", per_unit_cost_basis="0.00", # Free trial credits expiry_date="2025-04-08T23:59:59Z" # Match trial end date ) ``` Set `per_unit_cost_basis` to \$0.00 for trial credits to avoid charges. Set `expiry_date` to match the trial end date. ## Handling credit depletion ### Receive webhook notification When the customer depletes their credit balance, Orb sends a `customer.credit_balance_depleted` webhook to your endpoint: ```json theme={null} { "type": "customer.credit_balance_depleted", "properties": { "customer_id": "customer-18940", "pricing_unit": { "name": "credits" } } } ``` ### Block product access Do not block access based solely on the webhook. Webhook delivery order is not guaranteed. The customer may have purchased credits between depletion and webhook delivery. Instead, query the customer's current balance using the [fetch credit balance](/api-reference/credit/fetch-customer-credit-balance) API when you receive the webhook: ```python theme={null} balance = orb_client.customers.credits.fetch_by_external_id( external_customer_id="customer-18940", currency="credits", include_all_blocks=False # Only check currently active blocks ) if balance["balance"] == 0: # Block access in your system block_customer_access("customer-18940") ``` If the balance is 0, block access in your application. Update a status field in your database that your product checks before allowing usage. ### End trial early If the customer has depleted credits and has not added a payment method, end the trial: ```python theme={null} # Check if customer has payment method customer = orb_client.customers.fetch_by_external_id( external_customer_id="customer-18940" ) if not customer.get("payment_provider_id"): # No payment method, cancel subscription orb_client.subscriptions.cancel( subscription_id=subscription["id"], cancel_option="immediate" ) ``` ## Handling trial expiration ### 30-day limit webhook When 30 days elapse, Orb automatically cancels the subscription (based on the `end_date` you set). You'll receive a `subscription.ended` webhook: ```json theme={null} { "type": "subscription.ended", "subscription": { "id": "sub_...", "external_customer_id": "customer-18940", "status": "ended" } } ``` ### Block access on expiration When you receive this webhook, check if the customer has upgraded to a paid plan: ```python theme={null} # List customer's active subscriptions subscriptions = orb_client.subscriptions.list( external_customer_id="customer-18940", status="active" ) # Check if customer has an active Pro subscription has_active_paid_sub = any( sub["plan"]["external_plan_id"] == "pro" for sub in subscriptions["data"] ) if not has_active_paid_sub: # Trial expired without upgrade, block access block_customer_access("customer-18940") ``` ## Upgrading to paid plan When a customer wants to upgrade, collect payment information using Stripe's payment method collection flows. See Stripe's [save and reuse payment methods](https://docs.stripe.com/payments/save-and-reuse) documentation. Once the payment method is saved as the customer's default in Stripe, Orb syncs it automatically. ### Schedule plan change When the customer adds a payment method, upgrade them immediately to the Pro plan: ```python theme={null} orb_client.subscriptions.schedule_plan_change( subscription_id=subscription["id"], external_plan_id="pro", change_option="immediate", billing_cycle_alignment="plan_change_date" ) ``` Set `change_option=immediate` to upgrade right away. Set `billing_cycle_alignment=plan_change_date` to reset the billing cycle to start today and charge the full \$20 monthly fee immediately. This creates an invoice for \$20 and grants the customer their first 20-credit monthly allocation. ### Unblock product access After the plan change completes, unblock access: ```python theme={null} # Fetch updated subscription subscription = orb_client.subscriptions.fetch(subscription_id) if subscription["plan"]["external_plan_id"] == "pro": # Customer upgraded, restore access unblock_customer_access("customer-18940") ``` ### Credit persistence Credits granted during the trial persist through the upgrade because you granted them via the ledger entry API, not as a plan allocation. The customer keeps any remaining trial credits plus receives their first 20-credit monthly allocation from the Pro plan. ## Preventing double trials ### Check subscription history Before creating a trial subscription, verify the customer hasn't already had a trial: ```python theme={null} # Fetch subscription schedule to see plan history schedule = orb_client.subscriptions.fetch_schedule( subscription_id=subscription["id"] ) # Check if customer was ever on trial plan had_trial = any( phase["plan"]["external_plan_id"] == "trial" for phase in schedule ) if had_trial: # Customer already had trial, don't create another raise Exception("Customer already used trial") ``` Alternatively, maintain a `had_trial` boolean in your database when you create the first trial subscription. ### Subscription continuity When a customer upgrades from trial to paid, Orb maintains the same subscription resource. The subscription ID remains constant; only the plan changes. This means: * A customer can only have one subscription lifecycle * Plan changes don't reset trial eligibility * Subscription history provides a complete audit trail To check if a customer ever had a trial, list their subscriptions and examine the plan history via the fetch schedule endpoint. ## Manual operations ### Extending trials Extend a trial by updating the subscription's `end_date`: ```python theme={null} orb_client.subscriptions.update( subscription_id=subscription["id"], end_date="2025-04-15T23:59:59Z" # Extend by one week ) ``` If you also want to extend the credit expiration, create a new ledger entry with updated credits or modify the existing block's expiration. ### Compensatory credits Grant additional credits during the trial without charges: ```python theme={null} orb_client.customers.credits.ledger.create_entry_by_external_id( external_customer_id="customer-18940", entry_type="increment", amount=500, currency="credits", per_unit_cost_basis="0.00", description="Compensatory credits for service interruption", expiry_date="2025-04-08T23:59:59Z" ) ``` Set `per_unit_cost_basis` to \$0.00 to avoid invoicing. Use the `description` field to document the reason for the grant. # Use case guide: evolve existing pricing Source: https://docs.withorb.com/simulations/evolve-pricing Whether it’s reacting to changing market conditions, competitive threats, or customer feedback – pricing should evolve in lock step with the growth of your business. Simulations can help you instrument the pricing possibilities, and empower you to make the best decision – without any of the risk or experimentation debt. You have the full machinery of Orb's billing capabilities at your disposal, without any end-customer consequences. ## Experimenting with packaging Plans, or a pre-bundled set of prices, are great for simplifying how your customers buy your products. However, they can quickly become outdated as customer needs, willingness to pay, and market conditions change. If your historical “best sellers” are no longer driving the value of your plan, it’s time to refresh your packaging so you’re not leaving value on the table. Setting up that analysis is as easy as creating a [scenario](https://docs.withorb.com/simulations/introduction#creating-a-simulation) in Orb to simulate. To modify existing products in a plan, start from existing plan prices. Then add, remove, or modify usage or fixed fee prices to define your scenario. Build a simulation scenario from existing plan prices Alternatively, start from scratch for a more significant packaging update or plan refresh. ## Updating price rates To simulate rate changes for existing prices, make a copy of your existing pricing in a scenario. Then edit the individual rate or pricing model. Optionally, duplicate the scenario to simulate other pricing changes. Build your scenario options ## Simulating adjustments Whether you want to make an informed plan to ease the introduction of new pricing, or understand the impact of negotiated rates for your Enterprise business, simulate the impact of [adjustments](https://docs.withorb.com/product-catalog/adjustments#applying-adjustments-and-discounts) including discounts and spend maximums before rolling it out. ## Transitioning to a credits-first model Easily conduct an impact analysis of a credits-first pricing model before making the switch. Create a scenario with an [included allocation](https://docs.withorb.com/product-catalog/prepurchase#allocations) of prepaid credits to understand the revenue contribution of an upfront fixed commitment plus overage spend, relative to your current accurred pricing. Add allocations dialog in a simulation # Use case guide: launching new products Source: https://docs.withorb.com/simulations/new-product Monetizing new experiences is all about value capture and alignment. Depending on the stage and adoption of your product, Simulations can help you navigate first-time monetization, or turn pricing into a critical growth lever, rather than an operational roadblock.  ## Early products: transitioning from legacy pricing Navigating the shift from legacy subscription pricing to a usage-first monetization strategy is all about risk tolerance and mitigation. If you’ve just launched a new AI agent, folding this functionality into the price of your existing seat-based subscription won’t scale – as product adoption grows, so do your costs. Introduce usage-first pricing via a [scenario](https://docs.withorb.com/simulations/introduction#creating-a-simulation) in Orb Simulations. This will allow you to monitor product adoption and costs over time, and build a business case for when and how to price a new product, while staying aligned to your business goals and risk tolerance.  In the example below, by introducing a usage-price(s), I now have the optionality to consider: 1. Standalone pricing – charge for new products directly to de-risk the rest of my offerings 2. Hybrid pricing – reduce fixed fees alongside my usage based price(s) to reduce barriers to entry while maintaining repeatable revenue Review simulation summary ## Growth-stage products: finding your value metric For products entering the growth + scale stage, monetization is all about aligning product pricing with product value. Especially for AI builders, this means evaluating and transitioning between a few different metric types: * **Cost-centric** – pricing is directly tied to the unit economics of your product. In the early stages when adoption is low, the cost of using your new product or feature is passed through to your end customer.  * **Usage-first** – pricing is directly tied to overall adoption and usage, and typically commands a higher rate than strictly cost-centric pricing. * **Value-based / outcome-based** – pricing is directly tied to outcomes and product value. When your customers win, so do you. This typically commands the highest price point, and encourages further use of your product. \ In Orb Simulations, model out pricing for each metric as a separate scenario in the simulation. [Comparing the results](https://docs.withorb.com/simulations/introduction#actioning-on-insights) side by side will highlight the differences, implications, and tradeoffs of pricing on each metric. Build simulation scenarios ## Mature products: tune rates to the market Even for new products, especially innovative AI-enabled experiences, the ever changing market conditions and competitive pressure require constant refinement of your pricing.  To simulate rate changes for existing prices, make a copy of your existing pricing in a scenario. Then, edit the individual rate or pricing model. Optionally, duplicate the scenario to simulate other pricing changes. These simple tools allow you to quickly model out new pricing hypotheses and experiment with your pricing model. Build your scenario options # Sphere Source: https://docs.withorb.com/sphere # About ## **Provider-specific behaviors** * Unlike other tax integrations, you don't need to map Items in Orb to Products in Sphere — Sphere automatically pulls Items from Orb. ## **Geographic coverage** 100+ countries; all US states. # Setup ## **Prerequisites** 1. A Sphere account (sign up at [auth.getsphere.com/en/signup](http://auth.getsphere.com/en/signup) if you don't have one). 2. You need to enable the Orb integration on the Integrations page of your Sphere account. [Find instructions on Sphere’s website here](https://docs.getsphere.com/features/integrations/prebuilt-connectors/orb-integration). ## **Connecting your account** 1. Navigate to **Settings > Taxes**. 2. Select “Connect to Sphere.” 3. Enter your Sphere API key. # **Sample request and response payload** *The example below is illustrative of the shape Orb sends to and receives from Sphere today, based on Orb's tax provider processors and test fixtures. Exact field names and structure can change as Sphere versions its API — confirm against current behavior before relying on it for integration work.* ### ***Request (Orb → Sphere)*** ```text theme={null} { "transaction_id": "", "transaction_type": "invoice", "customer": { "id": "", "address": { "address1": "", "address2": "", "city": "", "state": "", "postal_code": "", "country": "" }, "tax_id": "", "metadata": {} }, "currency": "usd", "line_items": [ { "id": "", "amount": 10000, "product_id": "" } ] } ``` ### ***Response (Sphere → Orb)*** ```text theme={null} { "transaction_id": "invoice-123", "lines": [ { "id": "line-item-123", "tax_amounts": [ { "amount": 1000, "tax_rate": { "display_name": "", "percentage": 10, "taxability_reason": null } } ] } ] } ``` ## **Reporting tax for filing** Sphere is the one integration without a separate reporting or commit call. Orb's `report_tax(invoice)` for Sphere just calls the same `fetch_tax(invoice)` used for calculation. Sphere handles`void_tax(...)` by receiving updates about invoices and credit notes via webhook, on its own asynchronous schedule. # Stripe Source: https://docs.withorb.com/stripe # About ## **Geographic coverage** 100+ countries; all US states. # Setup ## **Prerequisites** 1. An active Stripe connection: Stripe Tax requires an existing Stripe integration for payments or invoicing. 2. Stripe Tax activated in your [Stripe Dashboard](https://dashboard.stripe.com/tax). ## **Connecting your account** 1. Navigate to **Settings > Taxes** in the Orb dashboard. 2. Select “Connect to Stripe Tax” (uses your existing Stripe connection). 3. Configure your default tax code for items without specific mappings. 4. Click Save. For Orb test mode, ensure Stripe Tax is activated in your Stripe test environment; for live mode, activate it in production. ## **Setting up tax codes** Orb supports tax code configuration per Item, used to map invoice line items to Stripe's tax codes. Configure under Settings > Items > Item name > edit, filling the External item ID with the Stripe tax code (e.g. txcd\_10000000 for general services). If no code is specified, Orb uses the default configured during setup (typically txcd\_10000000). # **Sample request and response payload** *The example below is illustrative of the shape Orb sends to and receives from Stripe Tax today, based on Orb's tax provider processors and test fixtures. Exact field names and structure can change as Stripe Tax versions its API — confirm against current behavior before relying on it for integration work.* ### ***Request (Orb → Stripe Tax)*** ```text theme={null} { "currency": "USD", "line_items": [ { "amount": 10000, "quantity": 1, "reference": "", "tax_code": "txcd_10000000" } ], "customer_details": { "address": { "line1": "", "line2": "", "city": "", "state": "", "postal_code": "", "country": "" }, "address_source": "billing", "tax_ids": [] }, "expand": [ "line_items.data.tax_breakdown" ] } ``` ### ***Response (Stripe Tax → Orb)*** ```text theme={null} { "id": "calc_123456", "amount_total": 11000, "currency": "usd", "tax_amount_exclusive": 1000, "line_items": [ { "reference": "", "amount_tax": 1000, "amount": 10000, "tax_rate": 0.1 } ], "tax_breakdown": [ { "tax_rate_details": { "display_name": "Local Sales and Use Tax" } } ] } ``` ## **Reporting tax for filing** Once the invoice is issued or finalized, Orb creates a tax transaction from the prior calculation via `stripe.tax.Transaction.create_from_calculation(...)`. Orb stores the returned transaction ID for future reversals (e.g. credit notes). ### ***Request (Orb → Stripe Tax, reporting)*** ```text theme={null} { "calculation": "calc_123456", "reference": "orb_invoice_inv_123" } ``` ### ***Response (Stripe Tax → Orb, reporting)*** ```text theme={null} { "id": "tax_transaction_123" } ``` # TaxJar Source: https://docs.withorb.com/tax-jar # About ## **Geographic coverage** US only — no international calculations for new customers. # Setup ## **Prerequisites** 1. A TaxJar account and API token. ## **Connecting your account** 1. Navigate to **Settings > Taxes** in the Orb dashboard. 2. Select “Connect to TaxJar.” 3. Enter your TaxJar API token and default tax code. ## **Setting up tax codes** Configure a `product_tax_code` per Orb Item under **Settings > Items > Item name > edit**. # **Sample request and response payload** *The example below is illustrative of the shape Orb sends to and receives from TaxJar today, based on Orb's tax provider processors and test fixtures. Exact field names and structure can change as TaxJar versions its API — confirm against current behavior before relying on it for integration work.* ### ***Request (Orb → TaxJar)*** ```text theme={null} { "shipping": 0, "to_country": "US", "to_zip": "94104", "to_state": "CA", "to_city": "San Francisco", "to_street": "345 California Floor 6", "line_items": [ { "id": "bjur5ox8eqwHXgNa", "quantity": 1, "product_tax_code": "custom-tax-code-price-id", "unit_price": 50, "discount": 0 } ] } ``` ### ***Response (TaxJar → Orb)*** ```text theme={null} { "amount_to_collect": 0, "breakdown": { "line_items": [ { "id": "", "combined_tax_rate": 0.08625, "tax_collectable": 0 } ] } } ``` ## **Reporting tax for filing** Once the invoice is issued or finalized, Orb separately reports the collected tax by creating an order transaction via POST `transactions/orders`. Unlike the calculation call, the reporting request includes a top-level `sales_tax` amount and a `transaction_date`, plus a per-line `sales_tax` on each line item. ### ***Request (Orb → TaxJar, reporting)*** ```text theme={null} { "transaction_id": "", "transaction_date": "2022-09-01T00:00:00+00:00", "to_country": "US", "to_zip": "94104", "to_state": "CA", "to_city": "San Francisco", "to_street": "345 California Floor 6", "amount": 50, "shipping": 0, "sales_tax": 4.13, "line_items": [ { "id": "bjur5ox8eqwHXgNa", "quantity": 1, "product_tax_code": "custom-tax-code-price-id", "unit_price": 50, "sales_tax": 4.13, "discount": 0 } ] } ``` # Make your first billable metric Source: https://docs.withorb.com/tutorials/first-billable-metric Billable metrics are a core feature that allows you to measure the usage events you send to Orb. These metrics power your usage-based pricing by tracking customer interactions and activities. This guide will walk you through creating your first billable metric in Orb and understanding its functions.