# 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.
# 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.

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**.

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.

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**.

### 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**.

4. In the **Details** section, click on the link under **Main network ACL**.

5. Click on the network ACL ID.

#### 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**.)

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.)

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.

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**.

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.

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**.

### 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**.

4. In the **Details** section, click on the link under **Main network ACL**.

5. Click on the network ACL ID.

#### 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**.)

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.)

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.

2. In the first step, name the new **Destination service account** and click **Create and Continue**.

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.

> ⚖️ **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.
>
> 
## 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.

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**.

6. In the **New principals** field, add the **Destination service account** created in **Step 1**, select the **Storage Admin** role, and click **Save**.

> ⚖️ **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**.

> 🔒 **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**.

## 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**.

3. Click the link to Create a **personal access token**.

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**.

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.
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.
After filling in the required information about your data destination, you will be able to choose which resource types you want to sync.
After creating a new data export connection, you'll be able to view it directly in the UI.
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**.

2. Click the **Create token** button to create a \*\* access token\*\*.

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**.

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.

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.

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. |
## 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 | - Filter out voided invoices using `voided_at` and filter out deleted invoices using `deleted_at`
- Filter to the desired date range using `invoice_date`
- Sum the `total`
|
| `credit_note_line_item` | Remove credit note amounts | - Filter out voided credit notes using `voided_at` and filter out deleted credit notes using `deleted_at`
- Filter to the desired invoice using `invoice_id`
- Sum the `total`
|
| `customer_balance_transaction` | Remove applied customer balances | - Filter to `action = 'applied_to_invoice'`
- Filter out deleted transactions using `deleted_at`
- 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 | - Filter our voided and deleted invoices used `voided_at` and `deleted_at` respectively.
- 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 | - Filter to `issued` or `synced` status
- 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 | - Filter out reverted entries with `reverted_at`
- 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
### 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.
## 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).
### 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.
### 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
#### Scheduled purchases
Quarterly or annual credit allocations:
* Align with enterprise budget cycles
* Bulk discounts for larger commitments
* Predictable procurement processes
## 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."
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.
#### 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.
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.
### 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.
To create a connection, use the "+ Add SSO Connection" button.
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.
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.
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.
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.
## 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.
# 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
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:
[](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
## 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.
### 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.
**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.
**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.
| 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.
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.
### 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.
## Collections settings
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.
# 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.
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.
# 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.
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.
### 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.
## 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
## 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.
## 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.
# Accounting impact (T-accounts)
## 1. Initial credit purchase
## 2. Application to usage invoices
# 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).
| |
| 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.
| |
## 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.
| |
| 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.
| |
## 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.
| |
| 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’.
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.
### 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**.
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.
### **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**.
5. Copy the **Certificate ID** from the list — you will need this value when installing the integration.
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:
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…**
### **If you choose to sync transactions for fixed-term subscriptions to a single Sales Order with related Sales Invoices in NetSuite…**
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.
# 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…**
### **If you choose to sync transactions for evergreen subscriptions as standalone Sales Orders in NetSuite…**
# 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…**
### **If you choose to sync one-off invoices as standalone Sales Orders in NetSuite…**
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.
# 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 |
| ---------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| - 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: - Open the relevant accounting period in NetSuite, then resync the invoice from Orb.
- OR, manually create a journal entry in NetSuite, then mark the invoice as synced in Orb.
- OR, do nothing, and cancel the sync.
|
| - 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: - Sync the invoice anyways, without the balance applied.
- Manually apply the credit memo that created this customer balance in NetSuite, then mark this invoice as synced.
- OR, do nothing, and cancel this sync.
|
| - 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: - 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** | - Name
- Email
- Billing address
- Shipping address
- External ID
- (Optional) mapped metadata at creation time
| - Company name
- Email
- defaultBilling in CustomerAddressbook
- defaultShipping in CustomerAddressbook
- Orb External Customer ID *(bundle custom field)*
- Mapped entity custom field
| When the first invoice is issued from Orb. *Note: Assumes first-time creation is enabled in integration settings.* |
| **Subscription** | - Subscription dates
- Subscription ID
| - Start date, End date
- 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** - Invoice date
- Invoice due date
- Invoice number
- Invoice memo
- Subsidiary
**Line level** - Item
- Name
- Rate
- Quantity
- Amount
- Service period start date
- Service period end date
- Taxable status (if applicable)
**Adjustments (only if applicable)** - Subtotal
- Discount percentage
- Discount amount
- Coupon amount
- Maximum adjustment
- Minimum adjustment
| **Transaction level** - Date
- Due date
- Orb invoice number *(bundle custom field)*
- Memo
- Subsidiary
**Line level** - Item
- Description
- Rate
- Quantity
- Amount
- Orb rev rec start date *(bundle custom field)*
- Orb rev rec end date *(bundle custom field)*
- Tax
**Adjustments (only if applicable)** - Orb list rate *(bundle custom field)*
- Orb discount percentage *(bundle custom field)*
- Orb discount amount *(bundle custom field)*
- Orb coupon applied *(bundle custom field)*
- Orb maximum adjustment *(bundle custom field)*
Orb minimum adjustment *(bundle custom field)* | When the invoice is issued from Orb. |
| **Credit Note** | **Transaction level** - Credit note creation date
- Credit note number
- Subsidiary
- Credit note memo
**Line level**\*Same as invoice.\*\*\*Adjustments (only if applicable)\*\**Same as invoice.* | **Transaction level** - Date
- Orb credit note number *(bundle custom field)*
- Subsidiary
- Credit note memo
**Line level**\*Same as invoice.\*\*\*Adjustments (only if applicable)\*\**Same as invoice.* | When the credit note is created in Orb. |
| **Payment** | - Amount
- Customer
- Transaction date (payment effective date, not “marked as paid”)
- Bank account
- Currency
- Linked invoice
| - Payment amount
- Customer
- Date
- Account
- Currency
- 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.
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.
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.
### 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.
### 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.
* 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.
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.
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.
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.
2. Once the package is installed, navigate to the App Launcher and search for the Orb app.
### 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.
## 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
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.
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).
## Setup
1. Navigate to the Integrations page under Settings.
2. Click "Connect to Salesforce" to configure the data sync.
3. Authorize Orb to connect to your Salesforce instance.
4. After a successful connection, you can configure the sync.
### 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.
### Viewing Data in Salesforce
Navigate to the Reports tab in Salesforce to view the synced data.
# 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.
### 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
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.
### 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."
If you need to manually trigger a webhook event to be sent to your server, you can also resend it in the UI.
## 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.
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”.
* 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.
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.
### 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.
**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.
All applied credit notes will appear on the invoice page.
### 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”.
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.
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).
**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
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
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.
### 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.
# 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).
# 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.
## 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.
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.
### 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.
### 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
### 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 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.
# 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.
### 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.
## 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.
### 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).
### 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.
# 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.
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.
To locally exclude specific customers or subscriptions from payment attempts, do so on the individual customer or subscription record.
### 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.
Choose "net terms" from the available templated date ranges in the dropdown, or provide a custom due date.
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 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.
### 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.
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.
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.
# **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.
**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 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).
### 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.
## 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.
### 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.
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.
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.
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.
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.
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.
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.
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.
## 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.
Additionally, line items on an invoice will denote the billed customers
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
2. **Add a price to the dimensional price group** (just like adding a price to a plan)
3. **Specify the price's dimension values** for the specific combination this price applies to
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.
### 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**.
*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.
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.
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**.
**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.
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**.
**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
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.
**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.
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.
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).
# 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.
## 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.
[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.
## 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.
## 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.
## 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.
# 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.
Any license overages incurred will be waived on the invoice, and accounted for in Orb Reports as contra revenue.
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
# 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.
## 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.
[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.
## 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.
## 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.
# 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.
### 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.
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.
### 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.
### 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.
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.
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
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.
## 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:
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.
### 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.
### 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.
### 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.
# 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.
## 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.
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".
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:
### 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".
## 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
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
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.
* 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**
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.
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:
### 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.
Our Premium plan is similar, but includes a \$50 monthly fee and 1 million tokens each month that do not rollover.
## 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`).
## 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.
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.
## 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:
# 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.
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.
## 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.
# 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
## 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.
## 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.
# 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.
## Step 1: What Are Billable Metrics?
Billable metrics in Orb help you measure and calculate usage data based on the events you send into the platform. For example:
* If you’re tracking **API calls**, the events for those API calls are sent to Orb, and the billable metric counts them.
* Beyond simple examples, billable metrics can handle more complex use cases, such as:
* Measuring **storage** used by a customer on a VM.
* Tracking **input and output tokens** for an AI model.
## Step 2: Creating a Billable Metric
To create your first metric, follow these steps:
1. **Navigate to the Billable Metrics Page** in test mode.
2. **Click on Create a New Metric**: You’ll have two options for how to define your metric:
1. **Simple Event Filter**: Use this to quickly match events based on properties such as event name (e.g., API call).
2. **Custom SQL Query**: For more advanced use cases, you can use SQL queries to define how your events should be processed.
## Step 3: Simple Event Filter
If you choose the simple event filter option:
* You can match events by their **event name**, such as `api_call`.
* This filter allows you to match additional fields within the event’s properties. For instance, if the event includes a key-value pair like `source`, you can add that to your filter.
* Orb will autofill values into the input based on previous events you’ve sent, making it easier to set up the filter.
*Previewing Events:*
Once the filter is set, you can preview past events to confirm the metric captures the correct data.
## Step 4: Defining the Calculation
After filtering the events, decide what type of calculation you want to perform:
* For example, if you’re counting API calls, you can set up a **count** of events where the event name is `api_call` .
* For more advanced calculations, you can define your property with a "Computed Property" expression.
## Step 5: Finalizing the Metric
Once you’ve defined the calculation, follow these steps:
1. **Name the Metric**: Give it a clear, descriptive name (e.g., “Count API Calls”).
2. **Define an Item**: The item is the sellable entity for this metric. For instance, if you’re selling API calls, you can create an item called “API Calls.”
3. **Publish the Metric**: You can add an optional description before publishing the metric.
## Step 6: Debugging Your Metric
Orb provides tools for debugging your billable metrics:
* In the **debugging tab**, you can filter by customer or date range to test your metric against existing events.
* This allows you to confirm that the metric produces the expected results before deploying it live.
## Step 7: Managing Billable Metrics
* **Duplicate Metrics**: If you need to create a similar metric, you can duplicate an existing one instead of starting from scratch.
* **Edit or Archive Metrics**: You can edit the metric’s name and description or archive metrics that are no longer in use.
* **Copy to Live Mode**: Once you’ve tested and validated the metric in test mode, you can easily copy it to live mode for production use.
## Step 8: Using Custom SQL Queries
For more complex use cases, you can use **Custom SQL** to build highly tailored metrics. Custom SQL allows for powerful extensions, making it suitable for various unique usage patterns. You can see some sample SQL expressions [here](https://docs.withorb.com/guides/extensibility/advanced-metrics).
By following this guide, you can create and configure billable metrics in Orb to track customer usage, power your pricing models, and ensure accurate billing for your services.
# Use the prepurchase ledger with custom credits
Source: https://docs.withorb.com/tutorials/first-custom-credits
The prepurchase credit ledger allows you to allocate a set amount of credits to customers that can be drawn down as they use your service. This system enables you to allocate credits either monthly, one-time, or based on your specific use case. Here's how you can set up and use it.
## Step 1: Configuring custom pricing units for credits
1. **Go to Settings** > **Pricing Units**:
1. You'll find a list of real-world currencies, such as USD.
2. **Custom Pricing Units** represent the credits that will be used in your pre-purchase credit ledger.
2. **Create a Custom Pricing Unit**:
1. **Identifier**: Used programmatically to reference credits.
2. **Display Name**: How the credits will appear in the Orb UI.
3. **Short Name**: How credits will be denoted on invoice line items (e.g., "consumed 2 `credits`").
## Step 2: Creating a plan with custom credit pricing
1. **Create a new plan**: For example, "Starter with Credits."
2. **Enable Custom Pricing Units**: When you add a usage-based price, you'll see a new input called **Price Currency**.
1. Select **Credits** instead of USD to charge using your custom credit unit.
2. Set up overage charges: If the customer runs out of credits, you can set a price for overages. For example, you might charge \$2 for every additional credit used beyond their allocation.
## Step 3: Using allocations to grant credits
**Allocations** allow you to automatically grant credits to customers on a recurring or one-time basis.
* **Recurring Allocation**: For example, you could give the customer 10 credits each month.
* **One-Time Allocation**: If you only want to provide credits upon sign-up.
* **No Rollover**: Credits will expire at the end of the month if they aren't used, preventing accumulation.
Once the plan is created, it will draw from the credit pool according to the usage prices and allocations defined.
Once you've created a plan that uses credits, you can begin managing customers' credit balances. This guide explains how to apply credits, track usage, and handle overages.
## Step 4: Adding a prepaid credit balance
1. **Go to the Customer Profile** and add a **Prepaid Credit Balance**:
1. Choose **Credits** (or USD, depending on your pricing unit).
2. Enter the number of credits (e.g., 10 credits) and the **Cost Basis** (e.g., \$2 per credit).
3. Set the **Effective Date**: This determines when the credit block becomes active for drawdown.
4. **Optional settings**:
1. You can add an expiration date for the credits.
2. Optionally, generate an invoice for the credit purchase (e.g., 10 credits at \$2 each will result in a \$20 invoice).
## Step 5: Creating a subscription to track usage
1. **Create the subscription** for the customer:
1. Select the plan that uses the credit-based pricing model (e.g., "Starter with Credits").
2. Align the subscription's effective date with the credit block's date to ensure they synchronize.
2. **Ingest usage events**:
1. Orb will automatically deduct credits as usage events are sent in. If you backdate the subscription, Orb will replay the ledger to apply the appropriate credits to past usage events.
## Step 6: Monitoring credit usage and overages
1. **View usage events**: As usage events are ingested, credits are deducted from the balance.
1. For example, if a customer uses 2 API calls, 2 credits will be deducted from their balance.
2. **Handling overages**:
1. If the customer exhausts their credit balance, overage charges will apply based on the plan's settings.
1. Example: If the customer runs out of credits and uses additional API calls, they will be charged \$2 per credit over the allocated amount.
## Step 7: Reviewing invoices with credits applied
1. **Draft invoices**: Orb will generate draft invoices that reflect the usage and credit consumption.
1. If the customer stays within their pre-purchased credit allocation, the invoice will reflect a **\$0 charge**.
2. If they exceed their credit balance, overage charges will appear on the invoice.
2. **Currency Matching**:
1. Orb ensures that usage for a price is deducted from the credit block's matching currency (e.g., USD or credits).
2. However, **fixed fees** will not be deducted from the credit balance.
1. For example, USD fixed fees will not be deducted from the USD credit balance.
3. **Viewing the Credit Ledger**:
1. You can view a detailed ledger showing credit increments and decrements.
2. Pending states indicate that a usage event is still within the grace period and could change before being "committed".
By following these steps, you can efficiently manage pre-purchased credits in Orb, handle customer usage, and ensure accurate billing based on credits and overages.
# Collect payment against your first issued invoice
Source: https://docs.withorb.com/tutorials/first-payment
Once you have created a subscription and issued invoices in Orb, the next step is to charge those invoices. This guide will show you how to set up payment workflows using Stripe Test Mode and simulate the complete payment process with test customers and fake credit cards.
## Step 1: Connecting to Stripe in Test Mode
1. **Enable Test Mode**:
1. Make sure you’re in **Test Mode** in Orb. This is a safe environment in which you can experiment with the full payment flow without affecting live customers.
2. **Connect to Stripe**:
1. Navigate to **Settings** > **Connections** in Orb.
2. If not already connected, you should see a **Connect to Stripe** button.
3. Click the button to begin the setup process. If you’re in Test Mode, Orb will automatically connect to Stripe’s Test Mode, allowing you to use test data ([fake credit cards](https://docs.stripe.com/testing), test customers, etc.).
## Step 2: Linking a Stripe test customer
1. Once Stripe is connected in Test Mode, go back to your **Customers** page in Orb.
2. Select the customer you want to use (e.g., “Test Customer 1”).
3. **Link the customer to Stripe**: Either via the Orb UI or API, connect your test customer to a corresponding Stripe customer in Test Mode.
## Step 3: Issuing an invoice
1. Navigate to the customer’s **invoices** page.
2. Find the invoice you want to issue (e.g., the prorated invoice for the subscription’s fixed fee).
3. Click **Issue Invoice**. This will move the invoice from an “Action Needed” state to an “Issued” state.
## Step 4: Testing auto-collection
* If **auto-collection** is enabled, Orb will attempt to collect payment automatically using the customer’s saved payment method.
* In this case, if the test customer doesn’t have a default payment method in Stripe, the auto-collection attempt will fail.
* To avoid this failure in the future, ensure the customer’s payment method is collected upfront during setup.
## Step 5: Manual payment via invoice portal
1. If auto-collection fails, or you want the customer to pay manually, use the **Invoice Portal**:
1. The **Invoice Portal** is an online interface that shows customers their invoice details, including usage and fees.
2. From the portal, customers can download PDFs, share the invoice, and proceed with payment.
2. **Pay the invoice**:
1. Simulate the customer’s experience by paying the invoice manually using a fake credit card provided by Stripe Test Mode.
* Example: Enter 4242 4242 4242 4242 as the credit card number, any future expiration date, a random CVV, and a zip code (e.g., 94105 for San Francisco).
2. Customers can also choose to **Save Payment Information** so that their card or bank account details are stored as the default payment method for future auto-collection.
3. Click **Submit Payment**. Stripe Test Mode will process the payment.
## Step 6: Confirm payment status
1. After submitting the payment, refresh the page to see the updated invoice status.
1. The invoice should now display as **Paid**.
2. The customer will see this status reflected in the Invoice Portal.
2. If you’re in a production environment with email notifications enabled, the customer will also receive an email confirming that their invoice has been paid.
## Step 7: Testing the full workflow
In Stripe Test Mode, you can repeat this flow with different test customers and payment methods to simulate the entire payment process.
* Payments made in Test Mode will only reflect within the Stripe Test environment and won’t create any live resources or transactions.
By following these steps, you can thoroughly test and simulate your Orb payment workflows to ensure a seamless experience when switching to a production environment.
See [Stripe's test mode documentation](https://docs.stripe.com/testing) for more testing information. The Stripe Services Agreement prohibits testing in live mode using real payment method details. Use your test API keys and the card numbers from Stripe's test mode documentation to test payment flows.
# Make your first plan
Source: https://docs.withorb.com/tutorials/first-plan
In this guide, we’ll walk you through setting up a plan in Orb to help you manage your usage-based pricing. Plans define how you structure your pricing, whether based on usage metrics, fixed pricing, or a combination of both.
## Step 1: What Are Plans?
Plans in Orb represent a template of prices to which you’ll subscribe customers. An example set of plans might be:
* **Free Plan**: Limited features, free of charge.
* **Starter Plan**: Basic paid plan.
* **Growth Plan**: More features, higher pricing.
## Step 2: Creating a New Plan
1. **Go to the Plans Page** in Orb and click **Create Plan**.
2. **Name the Plan**: For example, you can name it “Starter.”
3. **Optional External Plan ID**: This ID can be used in the API if you need to reference the plan externally, but you can skip this for now.
## Step 3: Defining Pricing Types
In Orb, you can choose between two types of pricing models for your plan:
* **Usage-based Pricing**: Powered by billable metrics that track usage (e.g., API calls).
* **Fixed Pricing**: A flat fee charged regardless of usage.
**Usage-Based Pricing**
1. Select **Usage-Based Pricing** and choose a **Billable Metric** to track usage. In this example, we’ll use a metric that counts API calls.
2. **Billing Cycle**: Set how often customers will be charged (e.g., monthly).
3. **Optional Additions**:
1. **Minimum Spend**: Set a minimum charge (e.g., customers must pay at least \$10 regardless of usage).
2. **Maximum Spend**: Cap the amount a customer can be charged.
3. **Discounts**: Add a discount by percentage, fixed amount, or usage amount.
## Step 4: Pricing Models
Orb supports several pricing models:
* **Unit Pricing**: Charge a flat rate per unit (e.g., \$1 per API call).
* **Tiered Pricing**: Charge different rates based on tiers of usage (e.g., \$0.50 per call for the first 1,000 calls, then \$1 after that).
* **Package Pricing**: Charge in increments, where partial usage of a package is considered a full package until the next one is reached.
* **Bulk Pricing**: Unlock a different rate for all units after a threshold has been reached
* **Matrix Pricing**: Set prices based on multiple factors (e.g., region and machine size).
For simplicity, we’ll choose **Unit Pricing** and set the price to **\$1 per API call**.
## Step 5: Adding a Fixed Price
If your plan also includes a fixed charge, such as a platform fee:
1. Choose **Fixed Price** and name it (e.g., “Platform Fee”).
2. Set the price (e.g., **\$10 per month**).
3. Choose whether this charge will be billed **in advance** or **in arrears**.
1. **In advance**: The fee is charged upfront at the start of the billing cycle.
2. **In arrears**: The fee is charged at the end of the billing cycle, along with usage charges.
## Step 6: Additional Options
* **Payment Terms**: Choose when payment is due (e.g., on issue, net 7 days, net 30 days).
* **Trial Period**: Offer a trial period where customers are charged 0% for a set number of days (e.g., the first 7 days are free).
* **Discounts, Minimum Spend, and Maximum Spent**: You can add plan-level discounts or define minimum/maximum spend limits across multiple prices.
## Step 7: Finalizing the Plan
1. **Preview**: Before creating the plan, review a preview of how it will appear.
2. **Create Plan**: Once everything is set up, click **Create** to publish the plan.
With these steps, you’ve successfully set up a pricing plan in Orb that combines usage-based and fixed pricing. This allows you to flexibly bill your customers based on how they use your service while maintaining the ability to charge fixed fees.
# Make your first subscription
Source: https://docs.withorb.com/tutorials/first-subscription
This guide will walk you through creating a customer and subscription in Orb. You’ll learn how to customize settings and subscription details for each customer.
## Step 1: Creating a customer
1. **Go to the Customers page** in test mode.
2. **Create a new customer** and enter the basic details:
1. **Customer name**: For example, “Test Customer 1.”
2. **Email address**: This will be used to send emails to customers when enabled in the account settings.
1. *Note*: Emails won’t be sent in test mode, so you don’t need to worry about accidental notifications.
3. **External Customer ID**: This is an alias or ID from your system for easy reference.
4. **Time zone**: Determines the customer’s billing periods alignment.
5. **Invoice method**: Choose **Orb Invoicing** and your preferred **payment gateway** (such as Adyen or Stripe).
6. **Optional settings**:
1. Add Stripe customer details to charge the customer in the future.
2. Add billing and shipping addresses for invoicing.
Once the customer is created, you can edit their email address, customer name, and tax information. However, the **time zone** and **external customer ID** cannot be changed after creation due to their impact on billing cycles.
## Step 2: Adding a subscription
1. **Click Add Subscription** for the customer.
2. **Select a plan**: Choose the plan you created earlier (e.g., Starter Plan).
3. **Customizing the subscription**:
1. You can add or remove **usage-based prices** or **fixed prices**.
2. Modify pricing terms, such as offering a discount or adjusting quantities (e.g., charging \$5 instead of \$10 for the platform fee).
3. No need to create separate plans for each customer—override plan prices during subscription creation.
## Step 3: Setting the billing start date
1. **Set the subscription start date** (e.g., September 1st).
2. **Anchor date**: You can choose to anchor billing to the start date or set a custom date (e.g., always invoice on the 5th of the month).
3. **Backdate subscriptions**: You can backdate up to three months, and any previous usage events will be picked up for billing.
## Step 4: Finalizing the subscription
1. **Proration**: If the subscription starts in the middle of a billing period, Orb will automatically prorate any fixed fees.
2. **Additional options**:
1. Add **minimum spend**, **discounts**, or other overrides specific to the subscription.
2. Adjust net payment terms or add memos to invoices.
Once the subscription is reviewed and finalized, Orb will display a timeline with a preview of the prices and billing schedule.
After creating a subscription for your customer, it’s essential to monitor and manage invoices, usage, and billing cycles. This guide explains how to view and edit invoices, manage billing terms, and handle proration.
## Step 5: Viewing subscription details
Once a subscription is active, you can view the following:
* **Subscription start date** and **billing cycle**.
* **Accrued usage**: Displays a preview of the current billing period’s usage (e.g., API calls).
* \*\*Upcoming invoice amount: \*\*the incurred cost on the end of billing period invoice.
## Step 6: Managing invoices
1. **Viewing invoices**: On the customer’s subscription page, you’ll see a timeline of invoices, including:
1. **In-advance charges**: These may include prorated charges for fixed fees if the subscription starts mid-month.
1. **Prorating fees**: If a subscription starts partway through a billing cycle, Orb will automatically adjust the fixed charges accordingly (e.g., reducing a \$10 platform fee to \$8.67 based on the service period).
2. **Draft invoices**: These accumulate usage charges, which are finalized at the end of the billing period.
3. **Invoice actions**:
1. In "Manual issuance" mode, click **Issue** to finalize an invoice.
1. If you have Stripe connected and emails enabled, automatic charges and email notifications will be triggered.
2. **Editing invoices**: You can edit invoice details, such as net payment terms, add fixed fees, or apply minimum spend and discounts before issuing.
## Step 7: Understanding the invoice grace period
**Usage-based pricing**: Invoices that include usage charges will only be issued after the grace period elapses.
* **Grace period**: The default is 12 hours after the billing period ends to ensure all usage events are captured before issuing the invoice.
* Once the grace period is over, the invoice can be issued manually or automatically.
## Step 8: Customizing invoice terms on the subscription
1. **Managing billing terms**:
1. You can adjust payment terms (e.g., net 7, net 14).
2. Add an **invoice memo** for clarity on future invoices.
2. **Reviewing usage**: Orb allows you to view usage data over time, including a detailed breakdown of events contributing to the invoice.
## Step 9: Automatic issuance and emails
* If configured, Orb can automatically issue invoices and send notifications to customers based on their billing schedule.
By following these steps, you can effectively manage customer subscriptions, track usage, and ensure accurate invoicing in Orb.
# Send your first usage event
Source: https://docs.withorb.com/tutorials/first-usage-event
This guide will walk you through the steps to send your first usage event into Orb using Postman. Follow along to ensure a smooth experience while experimenting in Orb’s test mode environment.
## Step 1: Enable test mode
* When starting, use **Test Mode** to experiment and test your Orb setup.
* In **Test Mode**, you can create test pricing plans, simulate different workflows, and send in dummy data.
* This environment allows you to see how events and usage data will appear on invoices.
## Step 2: Generate an API key
* To send events to Orb, you’ll need an API key.
* Navigate to the **Developers** section in the Orb dashboard and click on **API Keys**.
* Create a new API key, name it (e.g., “Test Key”). This key will interact with resources in **Test Mode**.
## Step 3: Set up Postman
* You’ll use Postman to make API calls in this tutorial. Postman is a tool for testing APIs.
* Once you’re ready to move into production, you can either use Orb’s SDKs or directly call the API.
## Step 4: Construct the event payload
Events are key to usage tracking and calculations in Orb. When creating your payload, ensure it adheres to this format:
* **Customer ID / External Customer ID**: Identifies which customer the event belongs to.
* The **Customer ID** refers to the internal Orb-generated ID.
* The **External Customer ID** is an alias you define.
* If you send events with just the external ID, Orb will accept them and link them once the customer’s subscription is created.
* **Timestamp**: This indicates when the event occurred.
* **Event Name**: Describes the type of event (e.g., API calls, product usage).
* **Properties**: Free-form key-value pairs where you can add additional information, such as regions, sources, or other event attributes.
* Values can be strings, booleans, or numbers.
* **Idempotency Key**: A unique identifier for your event to prevent duplicates.
* If the event is sent again within a specified grace period, Orb will ignore subsequent events with the same idempotency key.
**Example Payload Structure:**
```
{
"events": [
{
"external_customer_id": "cust-001",
"timestamp": "2023-09-03T14:30:00Z",
"event_name": "api_call",
"properties": {
"region": "us-west-2",
"source": "API Gateway"
},
"idempotency_key": "unique-key-001"
}
]
}
```
## Step 5: Send the event
* In Postman, ensure your API call is set to POST and your request body is structured correctly with the above event format.
* Adjust the **timestamp** to be within the grace period.
* Orb allows a default grace period of **12 hours** for late event reporting. If your timestamp exceeds this, update it to fit within that window.
## Step 6: Check the response
* Once the event is accepted, Orb will return a success message.
* You can enable **debug mode** to see details about the response, including any duplicates.
* If you send the same event multiple times within the grace period, Orb will recognize it as a duplicate and ignore the second instance.
**Troubleshooting Common Issues:**
* **Grace Period Exceeded**: If you attempt to send an event with a timestamp outside the grace period, adjust the timestamp to fit within the allowed window.
* **Duplicate Events**: If you see a duplicate error in `debug` mode, the same event was sent twice within the grace period using the same idempotency key. Subsequent duplicate events in the grace period will be ignored.
By following these steps, you can successfully send your first usage event into Orb, experiment with test data, and see how your usage data is processed in the system.
# Use the prepurchase ledger with multiple custom credit types
Source: https://docs.withorb.com/tutorials/multiple-custom-credits
Orb allows you to define multiple types of custom pricing units to manage various products or services under different credit pools. This feature is useful when you want separate credit allocations for different types of usage, such as API calls and transactions. Here’s how to set up and manage multiple custom pricing units for your customers.
## Step 1: Creating multiple custom pricing units
1. **Go to Settings** > **Pricing Units**:
1. You can define multiple custom pricing units based on the types of products or services you offer.
2. **Create Custom Pricing Units**:
1. Example: Create **API Credits** for tracking API usage and **Transaction Credits** for monitoring transaction completions.
2. You can name the credits based on their use case. These credits will act as separate pools of currency from which your usage prices can be drawn.
## Step 2: Setting up a plan with multiple custom pricing units
1. **Create a new plan**: Navigate to the **Plans** tab and click **New** to create a plan. For example, “Starter with Multiple Products.”
2. **Enable Custom Pricing Units**: This will allow you to configure different credit types for different usage metrics.
3. **Add Usage-Based Prices**:
1. Define a **Billable Metric** for each product. For instance:
1. Select **API calls** for the first usage-based price, assign **API Credits** as the pricing currency, and set the rate (e.g., 1 API credit per API call, each credit worth \$2).
2. For the second usage-based price, choose **Transactions Completed** and assign **Transaction Credits** with a rate of, for example, one transaction credit per transaction, each worth \$5.
This plan will draw down from the appropriate credit pool based on which product is being used (API calls or transactions).
## Step 3: Subscribing a customer to a plan with multiple credit pools
1. **Create a subscription** for a customer using the plan that contains multiple custom credit currencies (e.g., “Starter with Multiple Products”).
2. **Ingest Usage**: As usage events are ingested, Orb will draw from the relevant credit pool for each metric. For example:
1. API calls will deduct from the **API Credits** pool.
2. Transactions will deduct from the **Transaction Credits** pool.
3. **Managing Credits**: You can add credits to each pool individually:
1. Add credits to the **API Credit** pool if the customer is using API calls.
2. Add credits to the **Transaction Credit** pool if the customer is completing transactions.
3. Usage will be deducted from the corresponding credit pool based on the service used.
## Step 4: Handling overages and currency matching
1. **Overages**: If a customer exhausts their credit pool, overage charges will apply as defined in the plan.
1. Example: If a customer runs out of API credits, they might be charged \$2 for each additional API call.
2. **Currency Matching**: Orb ensures that the **currency of the credit pool** matches the **currency of the usage price**.
1. For instance, if the usage price is in **USD**, Orb will first draw from a USD credit pool (if it exists), and then charge overages in USD if the pool is exhausted.
2. For custom pricing units, you’ll need to define the conversion rate on the plan (e.g., 1 API credit equals \$2).
3. **Real-World Currencies**: The same logic applies to real-world currencies like USD. If your usage prices are in USD, Orb will deduct from any USD credit block before applying overage charges.
## Step 5: Viewing the credit ledger
1. **Track Credit Usage**: You can view a detailed breakdown of how credits are being used for different products.
1. The **Credit Ledger** will display increments and decrements for each credit pool.
2. You can backdate the credits up to three months, and Orb will adjust the ledger accordingly to account for previous usage events.
2. **Pending States**: The credit ledger may show pending states for usage events that are still within the grace period. Once the grace period elapses, these events will be finalized, and credits will be fully deducted.
# Walk through the Orb settings
Source: https://docs.withorb.com/tutorials/settings
This guide walks you through the key settings available in Orb, covering invoicing, email notifications, integrations, and more. Understanding these settings will help you customize your workflows and manage customer interactions efficiently.
## Invoice settings
* **Company Details**: Include your **company name**, **address**, and other relevant details. These will be displayed on your invoices.
* **Emails**:
* Set the **reply-to email address** that customers will use to respond to invoice notifications.
* Enable **email notifications** to inform customers when invoices are issued, paid, or when payments fail.
* Note: Email notifications do not trigger in **test mode**, but they do in **live mode**.
* **Invoice Formatting**:
* Add your **logo** to invoices.
* Set **invoice numbering** to either a **global sequence** or a **customer-specific sequence**.
* Include a **default memo** and a **footer** to be displayed on all invoices.
* **Invoice Control**:
* Decide whether invoices should be issued **automatically** (e.g., after the usage grace period) or **manually**.
* **Manual Issue**: You will need to manually review each invoice before issuance.
* **Auto-Issue**: Orb will automatically issue the invoices.
* **Failed Payment (Dunning)**:
* Configure a **dunning schedule** to retry failed payments at specific intervals (e.g., after 1, 4, or 9 days).
* **Invoice Portal**:
* This is a **customer-facing portal** where your clients can view their usage, invoices (paid or in draft), and other account details.
* Option to **hide voided invoices** from the portal.
* **Invoice Display**:
* You can enable **“Hide Zero Quantity Line Items”** to simplify the invoice display when there are multiple line items priced at \$0. This can be helpful if your plans include many products, but some items have not been used yet.
## Pricing units
* **Currencies**: Define the **currencies** available for your plans and custom pricing units.
* **Custom Pricing Units**: These are used to create credit-based pricing systems, as covered in a separate guide.
## Coupons
* **Coupons**: Create **reusable discounts** that can be applied during **subscription creation** or **plan changes** via the API.
## Items
* **Sellable Entities**: Define **items** that represent your products or services. These are used across different pricing models, line items, and billable metrics.
## Connections
* **Payment and Accounting Providers**: Orb integrates with several external systems to manage payments, invoicing, and accounting.
* **Adyen, Stripe**: Payment processing
* **Quickbooks, Billdotcom, Stripe:** Invoicing
* **Tax Providers**: If you need to apply taxes to your invoices, Orb integrates with:
* **TaxJar**
* **Avalara Avatax**
* Anrok
* Note: Once a tax provider is connected, it will automatically apply taxes to invoices upon issuance.
* **Accounting Sync**: If using **QuickBooks** or **NetSuite**, Orb will automatically sync invoices, payments, and credit notes with your chosen provider.
## User management
* **Invite Users**: Manage user access by inviting team members and assigning roles, such as **admins**.
## Security
* **Logging and Security**: Available in **live mode**, this setting allows you to control security logs and monitor user activities.
These settings give you the flexibility to tailor Orb to fit your company’s invoicing, payment, and customer interaction needs. By understanding and configuring these options, you can streamline your workflows and improve customer communication.
# Avalara (AvaTax)
Source: https://docs.withorb.com/untitled-page
# About
## **Provider-specific behaviors**
* Avalara uses a “commit” based call for reporting final tax amounts when the invoice is paid, unlike other providers that report tax at invoice issuance.
## **Geographic coverage**
Global — 190+ countries for VAT/GST; all US states for sales tax.
# Setup
## **Prerequisites**
1. **Organization address**: to accurately calculate tax with Avalara, Orb requires an account address set as the ShipFrom address in Avalara. Set this from the Orb dashboard under Settings > Invoices.
2. **Nexus in Avalara**: ensure your Avalara account is correctly configured with any nexus you're obligated to collect and remit tax in, including any newly added non-US/Canada nexus.
## **Connecting your account**
1. Navigate to **Settings > Taxes.**
2. Select “Connect to Avalara.”
3. Enter your Avalara username/password. We recommend provisioning a separate account for your Orb integration. Invalid credentials will error at this stage. For Orb test mode, use your Avalara sandbox credentials; for Orb live mode, use your Avalara production credentials.
## **Configuration options**
* Compute tax — when on, Orb always uses Avalara to compute tax on any invoice eligible for issuance. If a customer is tax exempt, Orb skips tax calculation for that customer.
* Commit tax — when on, Orb commits a transaction in Avalara when the invoice transitions from issued to paid. When off, Orb uses Avalara only for calculation, not reporting (see Avalara's docs on reportable transactions).
* Server side logging — when on, Orb logs requests and responses for every Avalara request. Logs can be made available on request via Slack.
* Avalara company code — the company Orb uses to create and report transactions.
## **Setting up tax codes**
Orb supports tax code configuration per Item, used during external syncs to ensure invoice line items map correctly to your tax/accounting services. Configure this under **Settings > Items > Item name > edit**. The external items dropdown is populated by Avalara's tax code list.
# **Sample request and response payload**
*The example below is illustrative of the shape Orb sends to and receives from Avalara AvaTax today, based on Orb's tax provider processors and test fixtures. Exact field names and structure can change as Avalara AvaTax versions its API — confirm against current behavior before relying on it for integration work.*
### ***Request (Orb → Avalara AvaTax)***
```text theme={null}
{
"companyCode": "def",
"customerCode": "",
"lines": [
{
"amount": 450,
"quantity": 1,
"taxCode": "default-tax-code",
"number": "abc",
"description": "Price 1",
"itemCode": "item_id",
"taxIncluded": false
}
],
"taxOverride": {
"type": "TaxDate",
"reason": "Invoice date",
"taxAmount": null,
"taxDate": "2022-09-01"
}
}
```
### ***Response (Avalara AvaTax → Orb)***
```text theme={null}
{
"lines": [
{
"lineNumber": "abc",
"isItemTaxable": true,
"lineAmount": 450,
"itemCode": "item_id",
"quantity": 1,
"reportingDate": "2022-09-01",
"tax": 39.38,
"taxableAmount": 450,
"sourcing": "Destination",
"taxCodeId": "default-tax-code",
"details": [
{
"rateRuleId": "",
"tax": 39.38,
"taxName": "State Tax",
"rate": 0.0875
}
]
}
]
}
```
## **Committing the transaction (reporting)**
Avalara is Orb's one true “commit” flow -- the transaction is created during the calculation call above, and then when the Commit tax setting is enabled, Orb later commits that same document when the invoice transitions from issued to paid — this is the one provider where the reporting step is tied to payment rather than issuance.
### ***Commit call (Orb → Avalara)***
```text theme={null}
commit_transaction(
company_code="",
transaction_code="",
model={"commit": True},
include={"documentType": "SalesInvoice"}
)
```
# Balance alerts
Source: https://docs.withorb.com/usage-alerting/balance-alerts
Detect when a customer’s prepaid‑credit balance is depleted, drops below a threshold, or recovers.
## Alert types
| Alert type | Fires when the prepaid ledger… | Webhook event |
| --------------------- | -------------------------------------------------------- | ----------------------------------- |
| **Balance depleted** | Transitions **> 0 → 0** | `customer.credit_balance_depleted` |
| **Balance dropped** | Transitions **≥ configured threshold → below threshold** | `customer.credit_balance_dropped` |
| **Balance recovered** | Transitions **0 → > 0** | `customer.credit_balance_recovered` |
## Evaluation mechanics
| Trigger category | Specific events | Notes |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- |
| Usage events | • Real‑time usage that draws down credits from the balance based on the billable metrics on the subscription. | See [Usage‑based triggers](/usage-alerting/overview) for more information about expected latencies. |
| Credit blocks | • Activation (manual creation, creation due to an automatic allocation on the plan, or as the result of an automated top‑up)
• Expiration or voiding | Block mutations may lag usage‑based triggers by a few minutes. |
| Alert config changes | • New balance alert created
• Alert threshold added or removed | Forces immediate re‑evaluation so the new configuration is active within seconds. |
## Fetching current alert state
Use the [fetch‑alert endpoint](/api-reference/alert/fetch-alert) to retrieve the serialized `balance_alert_status`.\
The object lists each threshold alongside a boolean indicating whether it is *currently* in alert.
## Practical considerations
* If the balance **drops below and then quickly rises above** a threshold, Orb may emit only the final (recovered) state. Rely on `balance_alert_status` serialized on the alert for the ground‑truth snapshot.
* A **depleted** balance on the ledger can lead to *immediate overage charges* depending on plan configuration if usage continues. In order to resolve this, you can:
* Manually add and backdate a credit block will cover existing overages.
* Alternatively, enable **top‑ups** to automate this workflow; you can think of a top‑up as a material side effect of a balance alert. Note
that [top-ups](/product-catalog/prepurchase#automatic-top-ups) also execute with the same tight SLAs as balance alerts, so will typically operate very quickly.
# Grouped cost alerts
Source: https://docs.withorb.com/usage-alerting/grouped-cost-alerts
Evaluate per‑group spend subtotals (e.g. per seat or tenant) instead of the single subscription invoice total.
A standard cost alert evaluates a single number—the running total of the subscription's draft invoice. A **grouped cost alert** instead breaks that total into per‑group subtotals and evaluates each group independently, firing once for every distinct group that crosses a threshold.
**This feature is in private preview.**
Grouped cost alerts are not included with all Orb billing plans by default. Contact your Orb account team to learn more.
Use a grouped cost alert when a single Orb subscription represents many of *your* customers, seats, projects, or workloads. Group spend by an event property to get a signal per group:
* **Per‑seat** spend caps in usage‑based seat pricing (`grouping_keys: ["seat_id"]`).
* **Per‑end‑user** budgets when you resell or wrap Orb (`grouping_keys: ["customer_id"]`).
* **Per‑model, per‑region** budgets in inference pricing (`grouping_keys: ["model", "region"]`).
To evaluate the same pre‑credit subtotal without splitting it by group, use a [spend alert](/usage-alerting/spend-alerts) instead.
Grouping is configured at creation time on a `cost_exceeded` alert. It cannot be changed afterward; create a new alert to change the grouping. A subscription (or plan) can have at most 3 grouped cost alerts.
## Configuration
| Field | Required | Description |
| --------------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `grouping_keys` | Yes | One or more event property keys (up to 5, no duplicates) to group spend by. Subtotals are computed per distinct combination of these keys' values. |
| `currency` | Yes (when grouping) | The case‑sensitive currency or custom pricing unit to scope the alert to. Required whenever `grouping_keys` is set, since subtotals can't be summed across pricing units. |
| `price_filters` | No | Restricts *which* prices contribute to the subtotals. Each filter matches on `price_id`, `item_id`, or `price_type` with an `includes` or `excludes` operator, combined with AND. For example, exclude a fixed platform fee so it doesn't inflate every group. |
| `threshold_overrides` | No | Per‑group threshold customization (see below). |
## Which prices contribute
Within the alert's currency (and any `price_filters`), every usage‑based and license price on the draft invoice contributes to the per‑group subtotals. Fixed fees and composite prices are excluded—a fixed fee has no usage to group by, and a composite price is derived from other line items rather than from an independent metric.
For **license prices**, grouped subtotals are computed as a per‑group `quantity × rate` and **do not account for per‑license pool drawdowns**. The grouped value is a usage‑attribution estimate at the marginal rate, not a reconciliation of the invoice. On license‑priced plans, the sum of per‑group subtotals will not match the line item's billed amount, which reflects the aggregate overage after drawdowns. Set thresholds accordingly.
## Per‑group threshold overrides
By default every group is evaluated against the same `thresholds`. `threshold_overrides` lets you tailor thresholds for specific groups—give an enterprise tenant a higher cap, or silence an internal test group entirely.
Each override maps a specific combination of `grouping_keys` values to a list of thresholds that **fully replaces** the defaults for that group:
* A non‑empty `thresholds` list replaces the default thresholds for that group.
* An **empty** `thresholds` list **silences** that group—it never fires.
* Groups without an override use the default `thresholds`.
An alert can have up to 50 threshold overrides.
Overrides are only valid on **subscription‑scoped** alerts. Plan‑scoped alerts reject `threshold_overrides`, because the relevant group values vary from subscription to subscription.
## Example
Create a subscription cost alert with [`POST /v1/alerts/subscription_id/{subscription_id}`](/api-reference/alert/create-subscription-alert) that fires at \$500 per `seat_id`, ignores the platform fee, raises the cap for one seat, and silences an internal seat:
```json theme={null}
{
"type": "cost_exceeded",
"thresholds": [{ "value": "500.00" }],
"grouping_keys": ["seat_id"],
"currency": "USD",
"price_filters": [
{ "field": "price_type", "operator": "includes", "values": ["usage"] }
],
"threshold_overrides": [
{ "group_values": ["seat_enterprise"], "thresholds": [{ "value": "5000.00" }] },
{ "group_values": ["seat_internal"], "thresholds": [] }
]
}
```
## Cardinality limit
Because each distinct group is evaluated separately, grouping on a high‑cardinality property (for example a per‑event ID) can produce an unbounded number of groups. To protect evaluation, Orb caps a grouped alert at **1,000 distinct groups per evaluation**. If a subscription exceeds that limit, Orb automatically disables the alert for that subscription and emits a [`subscription.alert_disabled`](/integrations-and-exports/webhooks#subscription-alert_disabled) webhook so you can react. Choose grouping keys with bounded cardinality (seats, tenants, models) rather than unbounded identifiers.
## Webhooks
When groups cross a threshold, Orb batches all groups that fired in the same evaluation timeframe into one [`subscription.grouped_cost_exceeded`](/integrations-and-exports/webhooks#subscription-grouped_cost_exceeded) webhook—rather than one webhook per group—with the timeframe window in the payload. A single webhook covers up to 1,000 groups, matching the cardinality limit above.
# Alerting overview
Source: https://docs.withorb.com/usage-alerting/overview
Configure cost, usage, and balance alerts and understand their evaluation SLAs.
## Alert types
| Alert type | Scope | Fires when… | Webhook event(s) |
| ---------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Subscription usage** | Upcoming subscription invoice + billable metric | Usage for the metric on the draft invoice crosses any configured threshold. | [`subscription.usage_exceeded`](/integrations-and-exports/webhooks#subscription-usage_exceeded) |
| **Subscription cost** | Upcoming subscription invoice | Running invoice total (after any credit offsets) crosses any configured monetary threshold. | [`subscription.cost_exceeded`](/integrations-and-exports/webhooks#subscription-cost_exceeded) |
| **Grouped subscription cost** | Upcoming subscription invoice, grouped by event property | A per‑group spend subtotal (e.g. per `seat_id`) crosses a configured monetary threshold. See [grouped cost alerts](/usage-alerting/grouped-cost-alerts). | [`subscription.grouped_cost_exceeded`](/integrations-and-exports/webhooks#subscription-grouped_cost_exceeded), [`subscription.alert_disabled`](/integrations-and-exports/webhooks#subscription-alert_disabled) |
| **Subscription spend** | Upcoming subscription invoice, before credits | Rated spend (line item subtotals before credit draw‑down) crosses a configured monetary threshold. See [spend alerts](/usage-alerting/spend-alerts). | [`subscription.spend_exceeded`](/integrations-and-exports/webhooks#subscription-spend_exceeded) |
| **Credit balance depleted** | Customer | Prepaid balance transitions **> 0 → 0**. | [`customer.credit_balance_depleted`](/integrations-and-exports/webhooks#customer-credit_balance_depleted) |
| **Credit balance dropped** | Customer | Prepaid balance transitions **≥ threshold → below threshold**. | [`customer.credit_balance_dropped`](/integrations-and-exports/webhooks#customer-credit_balance_dropped) |
| **Credit balance recovered** | Customer | Prepaid balance transitions **0 → above 0**. | [`customer.credit_balance_recovered`](/integrations-and-exports/webhooks#customer-credit_balance_recovered) |
| **License allocation replenished** | License | License allocation resets to **100%.** | `reset_allocations` |
| **License allocation depleted** | License | Remaining license allocation reaches **0%**. | `alert_configuration.thresholds` |
| **License allocation exceeds** | License | Remaining license allocation crosses any configured threshold. | `alert_configuration.thresholds` |
## Managing alert states
Alerts are often tied to a specific plan or *type* of subscription. Accordingly, Orb allows you to configure alerts on the plan itself and automatically propagates them to all subscriptions of that plan. This allows you to quickly add an alert to an existing plan without having to run a migration over existing subscriptions. Similarly, when an alert is disabled, this change applies to all subscriptions.
Note that alerts can be created, enabled, disabled, or modified either through the Orb UI (at the plan or subscription level) or through the [API](/api-reference/alert/create-customer-alert).
It's often useful to set ad hoc alerts for specific customers (especially when dealing with Enterprise contracts), so alerts can also be managed at the individual subscription level. Orb's subscription card allows you to view alerts inherited from the plan and change them for the specific subscription instance. With this workflow, you can create alerts that correspond to a customer's usage patterns, even if they differ from other subscriptions to the same plan.
## Viewing triggered alerts
Each subscription card on the customer page in Orb shows which alerts have been triggered during the current billing period, and the time at which the threshold was exceeded.
## Evaluation mechanics & SLAs
Most alerts and related usage monitoring actions like threshold invoicing and automatic top‑ups are evaluated in real-time, allowing you to build spend management workflows integrated into your product experience that are nearly instantaneous.
Orb also supports periodic evaluation for more complex metrics, even allowing you to set up alerting over metrics that require a view of the full billing period to evaluate. In this case, the alert is evaluated periodically, and the Orb dashboard will indicate the difference in evaluation cadence eligibility.
Note that an alert that composes multiple metrics (e.g. a balance alert that depends on drawdowns on all metrics or an invoice costs alerts that depends on the sum of all line items) will still be evaluated in real-time on the basis of the metrics that support incremental computation. In other words, if a large majority of your usage comes from simple, incrementally computable metrics, the alert will be evaluated in near real-time. The Orb dashboard will indicate that a subset of metrics are incrementally computable and cause the alert to be evaluated in real-time.
| Path | Eligibility | Latency |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| **Real‑time** | Draft invoices for the customer contain only **decomposable** line items (metrics that are possible to incrementally compute, e.g., `SUM` or `COUNT`). | Seconds to minutes |
| **Periodic** | A metric (e.g., `COUNT DISTINCT`, percentile metrics) is present that requires scanning the full events dataset for the billing period. | Periodically, but Orb provides an option for commercial add-ons on Advanced and Enterprise agreements that guarantee tight SLAs. |
Creating a new alert or modifying an existing alert's thresholds immediately
invalidates the customer so the alert is re‑evaluated on the appropriate path,
but this may take several minutes to complete.
## Consuming alerts via webhooks
All alerts generate a webhook event when they fire, and your system can use this event to trigger actions. If several thresholds are crossed in rapid succession, Orb sends only the event for the highest threshold.
[Grouped cost alerts](/usage-alerting/grouped-cost-alerts) instead emit a single `subscription.grouped_cost_exceeded` event that batches every group that crossed a threshold in the same evaluation, so handle its `groups` array rather than expecting one event per group.
# Spend alerts
Source: https://docs.withorb.com/usage-alerting/spend-alerts
Evaluate rated spend—the pricing subtotal before credits and invoice‑level adjustments—instead of the net invoice total.
A standard cost alert evaluates the running total of the subscription's draft invoice, measured after prepaid credits are drawn down. A **spend alert** instead evaluates **rated spend**—the sum of that invoice's line item subtotals in a single currency, measured before credit draw‑down and before invoice‑level adjustments.
**This feature is in private preview.**
Spend alerts are not included with all Orb billing plans by default. Contact your Orb account team to learn more.
Use a spend alert when you want to know what a customer consumed, independently of how that consumption is paid for:
* **Customers on prepaid credits.** While credits cover usage, they reduce the draft invoice total, so a cost alert may never fire for that consumption. A spend alert tracks the consumption itself, so you can warn a customer at \$10,000 of usage whether or not they have prepaid for it.
* **Spend caps on part of a subscription.** `price_filters` restrict which prices contribute, so a cap can cover inference usage and ignore a platform fee.
Spend is its own alert type, `spend_exceeded`, set at creation time. It is independent of the subscription's cost alert and of any [grouped cost alerts](/usage-alerting/grouped-cost-alerts): a subscription can run all three at once.
## Configuration
| Field | Required | Description |
| --------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `type` | Yes | `spend_exceeded`. |
| `thresholds` | Yes | Monetary values in the alert's currency. The alert fires when rated spend reaches or exceeds a threshold. Duplicate values are rejected. |
| `currency` | Yes | The case‑sensitive currency or custom pricing unit to scope the alert to. Only line items priced in this unit contribute, since subtotals can't be summed across pricing units. |
| `price_filters` | No | Restricts *which* prices contribute. Each filter matches on `price_id`, `item_id`, or `price_type` with an `includes` or `excludes` operator, combined with AND. For example, exclude a platform fee so it does not count against the cap. |
To evaluate the same subtotal broken out by an event property, use a [grouped cost alert](/usage-alerting/grouped-cost-alerts) instead.
## Spend and cost alerts
| Behavior | `cost_exceeded` | `spend_exceeded` |
| ------------------------- | ------------------------------------ | ---------------------------------- |
| Evaluates | The running draft invoice total | The rated subtotal in one currency |
| Credit draw‑down | Included—the total is net of credits | Excluded—the subtotal is gross |
| Invoice‑level adjustments | Included | Excluded |
| Fixed fees | Included | Excluded |
| `currency` | Not accepted | Required |
| `price_filters` | Not accepted | Supported |
| Webhook | `subscription.cost_exceeded` | `subscription.spend_exceeded` |
## Which prices contribute
Within the alert's currency (and any `price_filters`), every usage‑based and license price on the draft invoice contributes its line item subtotal. Fixed fees and composite prices are excluded—a fixed fee is not rated from usage, and a composite price is derived from other line items rather than from an independent metric. Because only metric‑backed prices contribute, a `price_filters` entry that includes only `fixed_in_advance` or `fixed_in_arrears` matches no prices and the alert never fires.
Those subtotals are summed before credit draw‑down, before invoice‑level adjustments such as discounts, minimums, and maximums, before conversion to the subscription's invoicing currency, and before taxes. Note that [threshold invoices](/invoicing/threshold-invoicing) already issued in the billing period do not reduce the value either.
For **license prices**, subtotals are computed as `quantity × rate` and **do not account for per‑license pool drawdowns**—the same as for [grouped cost alerts](/usage-alerting/grouped-cost-alerts). On license‑priced plans this will not match the line item's billed amount, which reflects the aggregate overage after drawdowns. Set thresholds accordingly.
## Examples
Create a subscription spend alert with [`POST /v1/alerts/subscription_id/{subscription_id}`](/api-reference/alert/create-subscription-alert) that fires at \$10,000 and \$25,000 of USD usage spend, ignoring non‑usage prices:
```json theme={null}
{
"type": "spend_exceeded",
"thresholds": [{ "value": "10000.00" }, { "value": "25000.00" }],
"currency": "USD",
"price_filters": [
{ "field": "price_type", "operator": "includes", "values": ["usage"] }
]
}
```
The same body creates a plan‑level alert at [`POST /v1/alerts/plan_id/{plan_id}`](/api-reference/alert/create-plan-alert), which Orb propagates to every subscription on that plan version.
Raise the thresholds later with [`PUT /v1/alerts/{alert_configuration_id}`](/api-reference/alert/update-alert). Omitting `price_filters` leaves the existing filters in place:
```json theme={null}
{
"thresholds": [{ "value": "20000.00" }, { "value": "50000.00" }]
}
```
## Managing spend alerts
The Orb dashboard displays spend alerts on the subscription's alerts card, alongside the thresholds crossed during the current billing period.
[`PUT /v1/alerts/{alert_configuration_id}`](/api-reference/alert/update-alert) updates `thresholds` and `price_filters`; omit `price_filters` to leave them unchanged, or pass an empty list to clear them. The [enable](/api-reference/alert/enable-alert) and [disable](/api-reference/alert/disable-alert) endpoints work as they do for every other alert. A plan‑level spend alert can also be disabled for an individual subscription.
Evaluation timing, behavior during plan changes, and real‑time versus periodic eligibility are the same as for other subscription alerts; see [subscription alerts](/usage-alerting/subscription-alerts).
## Limitations
* A subscription (or plan version) can have **one spend alert per currency**. A second alert in the same currency is rejected. Spend alerts do not count against the limit of 3 grouped cost alerts.
* `currency` cannot be changed after creation. Create a new alert instead.
* `metric_id`, `grouping_keys`, and `threshold_overrides` are rejected on a spend alert.
* Fixed fees and composite prices never contribute, whatever the `price_filters` say—see [which prices contribute](#which-prices-contribute).
* Spend alerts are created through the API. The dashboard displays them but has no creation flow for them.
## Webhooks
When rated spend crosses a threshold, Orb sends a [`subscription.spend_exceeded`](/integrations-and-exports/webhooks#subscription-spend_exceeded) webhook with the evaluation timeframe, the threshold that was crossed, and the evaluated amount. If several thresholds are crossed in rapid succession, Orb sends only the event for the highest threshold.
# Spend management
Source: https://docs.withorb.com/usage-alerting/spend-management
Use alerts and limits to help customers manage and cap their spend.
Alerts provide the core signal for any spend‑control workflow.
## Subscriptions with prepaid credits
* A [**balance depleted** alert](/usage-alerting/balance-alerts) indicates that the customer's prepaid credit ledger has been depleted,
and you should block product access or usage.
* To enforce a **hard cap** where the customer should always pay only for usage in advance, you must account for the brief delay between depletion and the alert:
* Set the conversion rate for prepaid credits to **\$0** (this effectively means that you absorb the cost of the overage and do not pass it onto your customer), **or**
* Charge the user for any overage per the assigned conversion rate of the price, **or**
* Add credits effective a past timestamp to cover the overage charges. Orb will automatically apply those credits against existing usage by replaying the credits ledger.
See the implementation guide on [product access with prepaid credits](/self-serve/product-access) for more information.
## Subscriptions with in-arrears billing
* Respond to the [**cost exceeded** alert](/usage-alerting/subscription-alerts#subscription-cost) by blocking product access or usage.
* Configure a **maximum** amount as an adjustment in your subscription configuration in addition to an alert.
* In the event that there's a delay between the maximum being reached and the alert being sent from Orb, the customer is never charged above the maximum
and the maximum limit will still be honored on the invoice.
## Per‑tenant and per‑seat spend control
When a single subscription represents many of your own customers, seats, or workloads, use a [grouped cost alert](/usage-alerting/grouped-cost-alerts) to enforce spend limits per group rather than across the whole subscription. Group by the relevant event property (e.g. `seat_id` or `customer_id`), set a default threshold, and use per‑group `threshold_overrides` to raise caps for specific tenants or silence internal ones.
## Building spend management in your application
Keep the following in mind when building spend management in your application:
1. Allow users to define **soft** and **hard** spend limits, each with multiple thresholds. Warning thresholds
should be used to notify the user that they are approaching the limit if hard limits are being used to block.
2. Provide a graceful **paused** state in your application when a limit is reached.
3. Send notifications—webhooks, email, or SMS—whenever a threshold is crossed.
4. Depending on your product contract, you may need to inspect the subscription state when you receive an alert to determine
the end of the billing period. This may be important to know when product access should be ungated, or a piece of infrastructure should
resume operation.
| Goal | Action |
| --------------------------------- | ----------------------------------------------------------------------------------------------------- |
| **Notify the user** | Use the webhook to trigger a notification over email, SMS, or webhook. |
| **Block product access or usage** | Disable product functionality until the next billing period begins (based on the subscription state). |
# Subscription alerts
Source: https://docs.withorb.com/usage-alerting/subscription-alerts
Configure cost and usage‑based alerts at the plan or individual subscription level.
## Defining subscription alerts
Both cost and usage alerts can be defined at the plan or subscription level. Plan alerts
can be disabled on a per-subscription basis, but cannot otherwise be modified without
creating a new subscription alert.
| Definition | Effect |
| ---------------------- | -------------------------------------------------------------------------------------------------- |
| **Plan level** | Every subscription created on the plan inherits the alert configuration. |
| **Subscription level** | The alert applies only to the targeted subscription and can override or augment plan‑level alerts. |
## Behaviour during subscription changes
| Change | Cost alerts | Usage alerts |
| ----------------------------------- | ---------------------- | -------------------------------------------------------------------------- |
| **Plan change** | Persist automatically. | Persist if the referenced billable metric still exists on the new plan. |
| **Migration to a new plan version** | Persist automatically. | Persist if the referenced billable metric still exists on the new version. |
## Evaluation mechanics
* Alerts are evaluated against the **current draft invoice** for the subscription.
* During the grace period after a billing period closes, Orb continues evaluating alerts on both:
1. The final invoice of the closed period.
2. The draft invoice for the new period.
* If a mid‑period plan change occurs, the billing period is split; Orb now evaluates alerts on the new draft invoice
which will be for a new period on the new plan.
## Cost alert calculation
* **Net cost basis**: Orb measures cost **after** credit draw‑down and **ignores** threshold invoices already issued in the same billing period.
* **Illustrative example**\
Assume an alert threshold of \$25 and a threshold invoice amount of \$10
1. Two threshold invoices have issued (2 × \$10 = \$20).
2. The draft invoice now shows \$5 in additional cost.
3. The cost alert fires on that draft invoice because the running total reaches \$25.
## Spend alerts
A spend alert evaluates rated spend—the sum of line item subtotals in a single currency—before credit draw‑down and invoice‑level adjustments, rather than the net cost basis above, so prepaid credits do not mask consumption. See [spend alerts](/usage-alerting/spend-alerts).
## Grouped cost alerts
A cost alert can evaluate per‑group spend subtotals—for example cost per `seat_id` or per tenant—and fire once for each group that crosses a threshold, instead of watching the single invoice total. See [grouped cost alerts](/usage-alerting/grouped-cost-alerts).
## Relationship to threshold invoicing
[Threshold invoicing](/invoicing/threshold-invoicing) follows the **same evaluation semantics** as cost alerts, and are evaluated
on the same tight SLAs. You can think of a threshold invoice as a *material* side‑effect of a cost alert: in addition to dispatching a webhook, Orb immediately issues an invoice for the accrued amount.
# Building a usage dashboard
Source: https://docs.withorb.com/usage-cost/usage-dashboard
A usage dashboard typically combines several views of a customer's account: the bill they're on track to be charged, the credits they have left, where their spend is coming from, and how usage is trending. Each view maps to a different Orb endpoint, with different performance, caching, and grouping characteristics. Choosing the right endpoint for each view, and composing them carefully on the page, is what keeps the dashboard fast and the numbers consistent with the invoice.
## What a usage dashboard usually contains
Common dashboard sections:
* A current period total — the bill the customer is on track to be charged at the end of the period.
* A credit balance — for prepaid plans, the remaining credits and the rate of consumption.
* A breakdown by group — top-N workspaces, projects, regions, or any cost-driving dimension. A specialized dimension in Orb is the [license scope](/product-catalog/license-pricing).
* A time series — usage or cost over the current billing period.
* An ad-hoc analytics surface — a freeform query workspace for power users and support.
Each section is best served by a different endpoint. A common source of bugs is using an endpoint optimized for one section in another — for example, using the upcoming invoice for a per-workspace breakdown, or running `prices/evaluate` on first paint.
## Bill preview vs. cost breakdown
There are two framings for what a customer sees:
* **Bill preview** shows what they'll be charged: line items, discounts, minimums, taxes, credits drawn down. The relevant number is `amount_due`, and the source of truth is the upcoming invoice.
* **Cost breakdown** shows where spend is coming from: which prices, which groups, which days. The relevant number is the subtotal, not the amount due.
Discounts, minimums, and maximums don't decompose by group, so avoid showing these in any breakdown concept. A 10% account-level discount can't be attributed to "this workspace contributed \$X to the discount," and a \$1,000 minimum can't be split across regions in any non-arbitrary way. A breakdown that shows "amount due by group" will produce numbers that don't reconcile to the invoice total.
In practice:
* The headline "what will I be charged" tile sources from the upcoming invoice.
* All breakdowns (by group, by day, by price) source from cost or subtotal endpoints, and the UI labels them as subtotals.
* Breakdown cards link out to the upcoming invoice for the full bill.
## Endpoints by dashboard section
| Dashboard section | Endpoint | Cacheable on first paint |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------ |
| Current bill total | [Upcoming invoice](/api-reference/invoice/fetch-upcoming-invoice) | Yes (cache header) |
| Credit balance | [Credit ledger](/api-reference/credit/fetch-customer-credits-ledger) / [credit blocks](/api-reference/credit/fetch-customer-credit-balance) | Yes (cache header) |
| Breakdown by group | Grouped costs *(private preview)* | Yes (precomputed) |
| Cost by license | [License usage](/api-reference/license/get-usage-for-all-licenses-of-a-type-on-a-subscription) | No |
| Time series (cost) | [Subscription costs](/api-reference/subscription/fetch-subscription-costs) / [customer costs](/api-reference/customer/fetch-customer-costs) | Yes (cache header) |
| Time series (usage) | [Subscription usage](/api-reference/subscription/fetch-subscription-usage) | Yes (cache header) |
| Freeform analytics | [`prices/evaluate`](/api-reference/price/evaluate-multiple-prices) | No (runs live) |
### Current bill total — upcoming invoice
[`GET /v1/invoices/upcoming?subscription_id=…`](/api-reference/invoice/fetch-upcoming-invoice) returns the draft invoice for the current period: line items, subtotal, total, and amount due. This is the only endpoint that runs the full bill computation (discounts, minimums, maximums, taxes), so it is the only correct source for the headline total.
The endpoint is scoped to a single subscription. For customers with multiple active subscriptions, call it once per subscription and render the results separately; discount and minimum math does not compose across subscriptions either.
Calling against a subscription whose status is `ended` returns a 400. Calling against a subscription that has no pending invoice (for example, a plan with no recurring fees) returns a 404; render a "no upcoming invoice" empty state rather than an error.
### Credit balance — credit ledger
[`GET /v1/customers/{id}/credits/ledger`](/api-reference/credit/fetch-customer-credits-ledger) returns the customer's credit ledger, scoped to a pricing unit, in newest-first order. The first entry's `ending_balance` is the current balance. Each entry includes a `type` (`increment`, `decrement`, `expiration_change`, `credits_expiry`, `void`, `void_initiated`), an `amount`, and a timestamp, so the same response can power the headline balance, a recent-activity feed, and a simple burn-rate projection.
If the customer has no ledger for the requested pricing unit, the endpoint returns an empty paginated list. Treat this as "no credits," not as an error.
For block-level detail — per-block expiry dates and cost basis — use [`GET /v1/customers/{id}/credits`](/api-reference/credit/fetch-customer-credit-balance). This returns a paginated list of credit blocks, useful for warnings such as "X credits expiring on date Y."
### Cost breakdown: grouped costs
**This feature is in private preview.**
Grouped costs are currently available to a limited set of customers. Reach out to your Orb contact to get access.
`POST /v1/subscriptions/{id}/grouped_costs` returns subtotals over a bounded window, grouped by a registered event property. Subtotals are read from precomputed data in Orb's system, so the endpoint is fast enough to call on first paint even at moderate cardinality.
The request body for `POST /v1/subscriptions/{id}/grouped_costs`:
```json theme={null}
{
"grouping_keys": ["workspace_id"],
"timeframe_start": "2026-05-01T00:00:00Z",
"timeframe_end": "2026-05-23T00:00:00Z",
"currency": "usd",
"limit": 10
}
```
The response echoes the requested timeframe and grouping, and returns the top buckets ordered by `subtotal` descending. `grouping_key` and each bucket's `grouping_value` are parallel lists, so composite groupings line up positionally. Subtotals are strings, and `has_more` is `true` when there were more groups than `limit`:
```json theme={null}
{
"timeframe_start": "2026-05-01T00:00:00Z",
"timeframe_end": "2026-05-23T00:00:00Z",
"currency": "usd",
"full_subtotal": "21043.78",
"grouping_key": ["workspace_id"],
"has_more": true,
"groups": [
{ "grouping_value": ["ws_4f8a2c"], "subtotal": "4821.55" },
{ "grouping_value": ["ws_91be07"], "subtotal": "3190.00" },
{ "grouping_value": ["ws_2d7c14"], "subtotal": "2756.83" },
{ "grouping_value": ["ws_a03f9b"], "subtotal": "1944.20" },
{ "grouping_value": ["ws_6e1d80"], "subtotal": "1502.97" },
{ "grouping_value": ["ws_b7c350"], "subtotal": "1188.40" },
{ "grouping_value": ["ws_0c9a26"], "subtotal": "947.11" },
{ "grouping_value": ["ws_5fd418"], "subtotal": "612.06" },
{ "grouping_value": ["ws_e84b71"], "subtotal": "433.89" },
{ "grouping_value": ["ws_77a2cd"], "subtotal": "208.50" }
],
"next_cursor": ["ws_77a2cd"]
}
```
For a composite grouping (for example `["org_id", "workspace_id"]`, which requires enablement), `grouping_key` and each `grouping_value` carry two parallel entries: `"grouping_value": ["org_a", "ws_4f8a2c"]`.
The response also returns `full_subtotal`—the subtotal of *all* groups over the queried period, not just the returned top `limit`—so you can display the total spend, the top groups, and any residual.
The endpoint is paginated. When `has_more` is `true`, pass the response's `next_cursor` back as the request's `cursor` to fetch the next page of groups, ordered by `subtotal` descending; `next_cursor` is `null` once there are no further groups. For a composite grouping, `cursor` and `next_cursor` are positional lists that line up with `grouping_keys`. Note that this endpoint does not group by day by default, unlike other cost endpoints.
The endpoint has several constraints; these are quotas that may be adjusted:
* The window between `timeframe_start` and `timeframe_end` cannot exceed 32 days by default.
* `timeframe_start` cannot be more than 100 days in the past.
* Each grouping key needs to be enabled per-account; a key that isn't allowlisted returns a 400.
* A single grouping key is allowed by default; composite grouping is supported but requires enablement.
* This endpoint will return a validation error if there are more than 5,000 distinct groups in the defined window for the subscription (regardless of how many are returned in the response).
Please reach out to Orb support if you have a use case that requires extended quotas.
For [license prices](/product-catalog/license-pricing), grouped subtotals are computed as a per‑group `quantity × rate` and **do not account for per‑license pool drawdowns**. The grouped value is a usage‑attribution estimate at the marginal rate, not a reconciliation of the invoice. On license‑priced plans, the sum of per‑group subtotals will not match the line item's billed amount, which reflects the aggregate overage after drawdowns. To understand allocation usage per license, use the dedicated [Cost by license](#cost-by-license) endpoint instead.
### Cost by license
If you're using Orb's [licenses feature](/product-catalog/license-pricing), you should fetch [usage per license](/api-reference/license/get-usage-for-all-licenses-of-a-type-on-a-subscription) using the dedicated, paginated API. Note that this endpoint accepts a `group_by`, allowing you to group usage by license, day, or both.
Day buckets and date windows are evaluated in the customer's timezone, matching license activation and deactivation dates. `remaining_credits` is never negative; when usage exceeds a license's allocation, it returns `0` while overage is reflected in `consumed_credits`.
Just like other cost data, license usage does not necessarily correlate to the `amount_due` on your invoice because of the presence of other adjustments and discounts. Relatedly, usage beyond a license's allocation may be deducted from other grants on the credit ledger, accrue as overage on the invoice, or be written off entirely depending on your license configuration. Therefore, we recommend you use this endpoint to understand how much each license has used of its allocation, and consider displaying the amount owed for the full customer separately.
### Time series — costs or usage
For a daily cost chart, use [`GET /v1/subscriptions/{id}/costs`](/api-reference/subscription/fetch-subscription-costs) for a single subscription, or [`GET /v1/customers/{id}/costs`](/api-reference/customer/fetch-customer-costs) to roll up across subscriptions that overlap the requested timeframe (overlapping days are summed). The response is one entry per day, each with `subtotal`, `total`, and a per-price breakdown.
The `view_mode` parameter controls how each datapoint is computed:
* `cumulative` (default) — each datapoint's `timeframe_start` is the start of the billing period, and `timeframe_end` advances by one day. Each datapoint represents "what the bill would be if the period ended today," including any minimums and discounts.
* `periodic` — each datapoint is the increment for that day, with a one-day window.
Use `cumulative` when the pricing involves minimums or discounts. Use `periodic` for a day-over-day bar chart, when the pricing doesn't have period-level adjustments that need to be reflected per-day.
For a daily usage chart (raw quantity, independent of pricing), use [`GET /v1/subscriptions/{id}/usage`](/api-reference/subscription/fetch-subscription-usage) with `granularity=day`. Usage can be grouped by an event property if `billable_metric_id` is also passed.
Grouping behavior depends on whether the metric is decomposable:
* Decomposable metrics (sums, counts) can be grouped by any event property.
* Non-decomposable metrics (unique counts) can only be grouped by the price's `invoice_grouping_key`. Per-day "uniques" do not sum to the period total, so arbitrary grouping is not mathematically meaningful.
For non-decomposable metrics where arbitrary grouping is required, use grouped costs (which operates in subtotal-space) instead.
Day buckets are demarcated by the customer's local midnight, including daylight savings. Both endpoints handle this server-side; do not re-bucket UTC timestamps on the client.
### Freeform analytics: prices/evaluate
[`POST /v1/prices/evaluate`](/api-reference/price/evaluate-multiple-prices) runs rating math dynamically over ingested events. The request body accepts:
* A `customer_id` or `external_customer_id`.
* A `timeframe_start` and `timeframe_end` (start cannot be more than 100 days in the past).
* A `price_evaluations` list of up to 100 prices. Each entry specifies one of `price_id`, `external_price_id`, or an inline price (a floating-price definition for what-if scenarios).
* A boolean filter expression per price evaluation, with computed properties supported.
* A `grouping_keys` list of properties or computed-property expressions (e.g. `hour_floor_timestamp_millis(timestamp_millis)` to bucket hourly).
* Optional `metric_parameter_overrides` for parameterized metrics.
Because evaluation is dynamic, there is no precompute and no cache. High-cardinality grouping or long timeframes can take on the order of seconds. Place this endpoint behind an explicit "Run query" affordance in the UI (a tab or button), not on the first-paint path.
For what-if flows that should evaluate against synthetic events rather than ingested usage (for example, proposal or quoting tools), use [`POST /v1/prices/evaluate_preview_events`](/api-reference/price/evaluate-preview-events). The body is the same as evaluate, plus an `events` array of synthetic preview events.
## Caching
Dashboards are read-heavy and first-paint-sensitive. The [cache header](/api-reference/cached-responses) is the main lever for keeping load times low:
```
Orb-Cache-Control: cache
Orb-Cache-Max-Age-Seconds: 300
```
When sent on a supported endpoint, Orb returns a precomputed snapshot if one is fresher than `Orb-Cache-Max-Age-Seconds`. The response includes an `Orb-Cache-Updated-At` header, which can be surfaced in the UI as "Updated N minutes ago." Without `Orb-Cache-Max-Age-Seconds`, any precomputed value is acceptable.
Cache support varies by endpoint:
| Cache behavior | Endpoints |
| ------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Honors the cache header** | Upcoming invoice, subscription costs, subscription usage, customer costs, credit ledger, and credit balance. |
| **Fast for a different reason** | Grouped costs reads precomputed metric values internally, so it is first-paint-safe regardless of headers. The cache header itself does nothing on this route. |
| **Never cached, by design** | `prices/evaluate` runs live every time. |
## Composing the dashboard
A typical per-customer dashboard, organized by load tier:
* **First paint, in parallel:**
* Upcoming invoice for each active subscription (headline total).
* Credit ledger (current balance, recent activity).
* Subscription costs with `view_mode=cumulative` or `periodic` (time series card).
* Grouped costs with the relevant grouping key (top-N breakdown).
* "Updated N minutes ago" indicator sourced from `Orb-Cache-Updated-At`.
* **On user-initiated refresh:**
* Re-issue the first-paint calls without the cache header (or with a small `Orb-Cache-Max-Age-Seconds`).
This gives roughly five or six cache-aware or precompute-backed calls on first paint, no dynamic rating queries on the load path, and no breakdown card attributing amount due.
## Rate limits
Orb's rate limits are [documented separately](/api-reference/rate-limits).
Think of these rate limits as the number of users who can concurrently do a fresh load of a usage dashboard, rather than those who can be browsing their usage. These rate limits are calibrated against our experience; in practice, we find that for accounts with millions of end-users, a small subset are active at any given time, a much smaller subset need a live view of their usage, and a fraction of those end users are re-loading their usage simultaneously.
## Notes on specific cases
* **Multi-subscription customers.** Most endpoints are subscription-scoped. Render per-subscription cards rather than collapsing across; [`GET /v1/customers/{id}/costs`](/api-reference/customer/fetch-customer-costs) is the exception for rolling up across subscriptions that overlap a timeframe.
* **Credit-denominated plans.** A currency value can be a [credit pricing unit](/product-catalog/credit-systems) rather than ISO 4217. Render credits as "1,200 credits," not "\$1,200.00."
* **Webhooks for lifecycle, cache for freshness.** [`invoice.issued`, `invoice.edited`, and `invoice.payment_succeeded`](/integrations-and-exports/webhooks) are appropriate signals for refreshing the headline tile when discrete invoice events occur. There is no webhook that fires when the upcoming invoice total changes by a small amount; that kind of continuous freshness is the cache header's job. `subscription.usage_exceeded` and `subscription.cost_exceeded` fire when a configured threshold is crossed, and are appropriate for alert UIs rather than general invalidation.
## Common pitfalls
* **Do not display a grouped breakdown of `amount_due`.** Discounts, minimums, and maximums do not decompose by group. Breakdown cards work in subtotal-space; link out to the upcoming invoice for the bill.
* **Do not put `prices/evaluate` on the first-paint path.** It is a dynamic endpoint. Place it behind an explicit tab and "Run" button.
* **Do not re-bucket timestamps client-side.** Day boundaries respect the customer's timezone on the server. Client-side re-bucketing breaks during DST and for non-UTC customers.
* **Do not expect grouped costs to honor the cache header.** The endpoint is fast for a different reason; sending the headers has no effect.