Manage Customer Balances

Customer balances are prepaid accounts that are automatically debited as usage ticks are reported. Top up balances via API, payment checkout, or external payment systems.


Top Up Balance with Payment (Recommended)

Create a charge and redirect customers to checkout. When payment succeeds, the balance is automatically topped up.

Endpoint

POST
/v1/metered-billing/balances/top-up-with-payment

Authorization: Bearer <ORVION_API_KEY> or X-API-Key: <ORVION_API_KEY>

Content-Type: application/json

Request Body

FieldTypeRequiredDescriptionExample
customer_refstring
Required
Your internal customer identifieruser_123
currencystring
Required
ISO currency code (3 characters)USD
amountstring
Required
Amount to add as a decimal string100.00
return_urlstring
Required
URL to redirect customer after payment succeedshttps://yourapp.com/dashboard
descriptionstring | null
Optional
Description for this top-upMonthly prepayment
metadataobject | null
Optional
Free-form JSON object stored with the transaction{"source": "dashboard"}
receiver_config_idstring | null
Optional
Optional receiver config ID for payment routingconfig_abc123
flow_slugstring | null
Optional
Optional billing flow slug for payment routingmainnet-usdc

Response

FieldTypeRequiredDescriptionExample
charge_idstring
Optional
Charge/transaction IDtxn_abc123
checkout_urlstring
Optional
Checkout URL to redirect customerhttps://pay.orvion.sh/checkout/txn_abc123
amountstring
Optional
Amount to be charged100.00
currencystring
Optional
Currency codeUSD
customer_refstring
Optional
Customer referenceuser_123

Example: cURL

cURL
curl -X POST "https://api.orvion.sh/v1/metered-billing/balances/top-up-with-payment" \
-H "Authorization: Bearer $ORVION_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_ref": "user_123",
"currency": "USD",
"amount": "100.00",
"return_url": "https://yourapp.com/dashboard",
"description": "Monthly prepayment"
}'

Example: Python SDK

Python
from orvion import OrvionClient
# Initialize client
orvion = OrvionClient(api_key="your_api_key")
# Top up balance with payment
result = await orvion.metered_billing.top_up_balance_with_payment(
customer_ref="user_123",
currency="USD",
amount="100.00",
return_url="https://yourapp.com/dashboard",
description="Monthly prepayment"
)
print(f"Charge ID: {result.charge_id}")
print(f"Checkout URL: {result.checkout_url}")
# Redirect customer to checkout_url

Example: Node.js SDK

JavaScript
import { OrvionClient } from '@orvion/sdk';
// Initialize client
const orvion = new OrvionClient({ apiKey: 'your_api_key' });
// Top up balance with payment
const result = await orvion.meteredBilling.topUpBalanceWithPayment({
customerRef: 'user_123',
currency: 'USD',
amount: '100.00',
returnUrl: 'https://yourapp.com/dashboard',
description: 'Monthly prepayment'
});
console.log(`Charge ID: ${result.chargeId}`);
console.log(`Checkout URL: ${result.checkoutUrl}`);
// Redirect customer to checkoutUrl

Response Example

{
  "charge_id": "txn_abc123",
  "checkout_url": "https://pay.orvion.sh/checkout/txn_abc123",
  "amount": "100.00",
  "currency": "USD",
  "customer_ref": "user_123"
}

Payment Flow

1. Create Top-Up Charge         2. Redirect to Checkout         3. Customer Pays
   ┌─────────────────────┐          ┌─────────────────────┐          ┌─────────────────────┐
   │                     │          │                     │          │                     │
   │ POST /top-up-with-  │          │ Redirect to        │          │ Customer connects   │
   │ payment             │    ──▶   │ checkout_url        │    ──▶   │ wallet and pays     │
   │                     │          │                     │          │                     │
   │ Returns:            │          │ pay.orvion.sh/      │          │ Transaction         │
   │ - charge_id         │          │ checkout/txn_abc123 │          │ confirmed on-chain   │
   │ - checkout_url      │          │                     │          │                     │
   └─────────────────────┘          └─────────────────────┘          └─────────────────────┘
                                                                              │
                                                                              ▼
4. Payment Confirmed               5. Auto Top-Up Balance           6. Redirect Back
   ┌─────────────────────┐          ┌─────────────────────┐          ┌─────────────────────┐
   │                     │          │                     │          │                     │
   │ Webhook:            │          │ Balance topped up   │          │ Customer redirected │
   │ billing.transaction.│    ──▶   │ automatically       │    ──▶   │ to return_url       │
   │ succeeded           │          │                     │          │                     │
   │                     │          │ Balance: $100.00    │          │ ?charge_id=txn_abc  │
   │ Metadata:           │          │                     │          │ &status=succeeded   │
   │ purpose: balance_   │          │                     │          │                     │
   │ topup               │          │                     │          │                     │
   └─────────────────────┘          └─────────────────────┘          └─────────────────────┘

Automatic Balance Top-Up

When payment succeeds, Orvion automatically:

  1. Detects the transaction has metadata.purpose: "balance_topup"
  2. Extracts customer_ref and balance_currency from metadata
  3. Tops up the customer's balance with the payment amount
  4. Creates a ledger entry linking the transaction to the balance

No webhook handler needed - it's handled automatically by Orvion!


Top Up Balance (Manual)

Add funds to a customer's balance directly (for manual credits, refunds, or external payments).

Endpoint

POST
/v1/metered-billing/balances/top-up

Authorization: Bearer <ORVION_API_KEY> or X-API-Key: <ORVION_API_KEY>

Content-Type: application/json

Request Body

FieldTypeRequiredDescriptionExample
customer_refstring
Required
Your internal customer identifieruser_123
currencystring
Required
ISO currency code (3 characters)USD
amountstring
Required
Amount to add as a decimal string100.00
descriptionstring | null
Optional
Description for this top-upMonthly prepayment
metadataobject | null
Optional
Free-form JSON object stored with the ledger entry{"payment_id": "pay_123", "source": "stripe"}

Example: cURL

cURL
curl -X POST "https://api.orvion.sh/v1/metered-billing/balances/top-up" \
-H "Authorization: Bearer $ORVION_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"customer_ref": "user_123",
"currency": "USD",
"amount": "100.00",
"description": "Monthly prepayment",
"metadata": {
"payment_id": "pay_123",
"source": "stripe"
}
}'

Example: Python SDK

Python
from orvion import OrvionClient
# Initialize client
orvion = OrvionClient(api_key="your_api_key")
# Top up balance
balance = await orvion.metered_billing.top_up_balance(
customer_ref="user_123",
currency="USD",
amount="100.00",
description="Monthly prepayment",
metadata={
"payment_id": "pay_123",
"source": "stripe"
}
)
print(f"Balance ID: {balance.id}")
print(f"Available: ${balance.available_amount}")

Example: Node.js SDK

JavaScript
import { OrvionClient } from '@orvion/sdk';
// Initialize client
const orvion = new OrvionClient({ apiKey: 'your_api_key' });
// Top up balance
const balance = await orvion.meteredBilling.topUpBalance({
customerRef: 'user_123',
currency: 'USD',
amount: '100.00',
description: 'Monthly prepayment',
metadata: {
payment_id: 'pay_123',
source: 'stripe'
}
});
console.log(`Balance ID: ${balance.id}`);
console.log(`Available: $${balance.availableAmount}`);

Response Example

{
  "id": "bal_abc123",
  "organization_id": "org_xyz",
  "customer_ref": "user_123",
  "currency": "USD",
  "available_amount": "100.00",
  "low_balance_threshold": null,
  "created_at": "2025-01-15T10:00:00Z",
  "updated_at": "2025-01-15T10:00:00Z"
}

Public Balance Top-Up (For Consumers)

Allow consumers who are not in the Orvion ecosystem to top up their balances directly without requiring API keys or authentication. This is perfect for end-users who only know their customer_ref.

Endpoint

POST
/v1/top-up/{customer_ref}

Authentication: None (public endpoint)

Content-Type: application/json

Request Body

FieldTypeRequiredDescriptionExample
amountstring
Required
Amount to top up as decimal string100.00
currencystring
Optional
Currency code (default: USD)USD
organization_idstring | null
Optional
Organization ID to use when multiple merchants have the same customer_ref. Required if multiple matches found.org_abc123
return_urlstring | null
Optional
URL to redirect after payment (defaults to top-up page)https://yourapp.com/balance
descriptionstring | null
Optional
Optional description for the top-upMonthly prepayment

Example: cURL

cURL
# Top up balance for a customer
curl -X POST "https://api.orvion.sh/v1/top-up/user_123" \
-H "Content-Type: application/json" \
-d '{
"amount": "100.00",
"currency": "USD",
"description": "Balance top-up"
}'

Response Example

{
  "charge_id": "txn_abc123",
  "checkout_url": "https://pay.orvion.sh/checkout/txn_abc123",
  "amount": "100.00",
  "currency": "USD",
  "customer_ref": "user_123"
}

Get Balance Info (Public)

Check balance information before topping up:

GET
/internal/top-up/{customer_ref}

Query Parameters:

  • currency (optional, default: USD) - Currency code
  • organization_id (optional) - Organization ID to use when multiple merchants have the same customer_ref

Example:

cURL
# Get balance info
curl -X GET "https://api.orvion.sh/v1/top-up/user_123?currency=USD"

Response:

{
  "customer_ref": "user_123",
  "currency": "USD",
  "available_amount": "75.50",
  "organization_name": "Acme Corp"
}

Public Top-Up Flow

1. Consumer Visits Page         2. Check Balance           3. Enter Amount
   ┌─────────────────────┐          ┌─────────────────────┐          ┌─────────────────────┐
   │                     │          │                     │          │                     │
   │ Visit:              │          │ GET /top-up/        │          │ User enters amount  │
   │ /top-up/user_123    │    ──▶   │ {customer_ref}      │    ──▶   │ and clicks "Top Up" │
   │                     │          │                     │          │                     │
   │ Shows current       │          │ Returns balance     │          │ POST /top-up/       │
   │ balance (if exists)  │          │ info                │          │ {customer_ref}      │
   └─────────────────────┘          └─────────────────────┘          └─────────────────────┘
                                                                              │
                                                                              ▼
4. Create Charge               5. Redirect to Checkout         6. Customer Pays
   ┌─────────────────────┐          ┌─────────────────────┐          ┌─────────────────────┐
   │                     │          │                     │          │                     │
   │ System finds org    │          │ Redirect to        │          │ Customer connects   │
   │ from balance        │    ──▶   │ checkout_url       │    ──▶   │ wallet and pays     │
   │                     │          │                     │          │                     │
   │ Creates charge with │          │ pay.orvion.sh/     │          │ Transaction         │
   │ metadata:           │          │ checkout/txn_abc   │          │ confirmed on-chain   │
   │ purpose: balance_   │          │                     │          │                     │
   │ topup               │          │                     │          │                     │
   └─────────────────────┘          └─────────────────────┘          └─────────────────────┘
                                                                              │
                                                                              ▼
7. Payment Confirmed               8. Auto Top-Up Balance           9. Redirect Back
   ┌─────────────────────┐          ┌─────────────────────┐          ┌─────────────────────┐
   │                     │          │                     │          │                     │
   │ Webhook:            │          │ Balance topped up   │          │ Customer redirected │
   │ billing.transaction.│    ──▶   │ automatically       │    ──▶   │ to return_url       │
   │ succeeded           │          │                     │          │                     │
   │                     │          │ Balance: $175.50    │          │ /top-up/user_123?   │
   │ Metadata:           │          │                     │          │ currency=USD        │
   │ purpose: balance_   │          │                     │          │                     │
   │ topup               │          │                     │          │                     │
   └─────────────────────┘          └─────────────────────┘          └─────────────────────┘

Frontend Integration

You can direct consumers to the hosted top-up page:

https://pay.orvion.sh/top-up/{customer_ref}?currency=USD

Or integrate the API directly into your own UI:

JavaScript
// JavaScript example
async function topUpBalance(customerRef, amount, currency = 'USD') {
const response = await fetch(`https://api.orvion.sh/v1/top-up/${customerRef}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: amount.toString(),
currency,
description: 'Balance top-up',
}),
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Failed to create top-up');
}
const data = await response.json();
// Redirect to checkout
window.location.href = data.checkout_url;
}
// Usage
topUpBalance('user_123', 100.00, 'USD');

Important Notes

  • No Authentication Required: These endpoints are public and don't require API keys
  • Organization Discovery: The system automatically finds the organization from the customer's existing balance. If multiple merchants have the same customer_ref, you must provide organization_id to disambiguate
  • Multiple Merchants: If multiple merchants match, the API returns a 400 Bad Request error listing the available merchants. Include organization_id in your request to specify which merchant to use
  • Automatic Top-Up: When payment succeeds, the balance is automatically topped up via webhook handler
  • Balance Must Exist: A balance must be created first (via merchant API) before consumers can top up
  • Currency Matching: The currency must match the existing balance currency

List Balances

Get all balances for your organization, optionally filtered by customer.

Endpoint

GET
/v1/metered-billing/balances

Query Parameters:

FieldTypeRequiredDescriptionExample
customer_refstring | null
Optional
Filter by customer referenceuser_123
limitnumber
Optional
Maximum number of balances to return (default: 100)100
offsetnumber
Optional
Pagination offset (default: 0)0

Example: cURL

cURL
# List all balances
curl -X GET "https://api.orvion.sh/v1/metered-billing/balances" \
-H "Authorization: Bearer $ORVION_API_KEY"
# Filter by customer
curl -X GET "https://api.orvion.sh/v1/metered-billing/balances?customer_ref=user_123" \
-H "Authorization: Bearer $ORVION_API_KEY"

Response Example

[
  {
    "id": "bal_abc123",
    "organization_id": "org_xyz",
    "customer_ref": "user_123",
    "currency": "USD",
    "available_amount": "75.50",
    "low_balance_threshold": null,
    "created_at": "2025-01-15T10:00:00Z",
    "updated_at": "2025-01-15T11:30:00Z"
  },
  {
    "id": "bal_def456",
    "organization_id": "org_xyz",
    "customer_ref": "user_456",
    "currency": "EUR",
    "available_amount": "50.00",
    "low_balance_threshold": null,
    "created_at": "2025-01-15T09:00:00Z",
    "updated_at": "2025-01-15T09:00:00Z"
  }
]

View Balance Ledger

View all transactions (top-ups and usage deductions) for a specific balance.

Endpoint

GET
/v1/metered-billing/balances/{balance_id}/ledger

Query Parameters:

FieldTypeRequiredDescriptionExample
limitnumber
Optional
Maximum number of entries to return (default: 100)100
offsetnumber
Optional
Pagination offset (default: 0)0

Example: cURL

cURL
curl -X GET "https://api.orvion.sh/v1/metered-billing/balances/bal_abc123/ledger" \
-H "Authorization: Bearer $ORVION_API_KEY"

Response Example

{
  "entries": [
    {
      "id": "ledger_001",
      "balance_id": "bal_abc123",
      "amount": "100.00",
      "type": "credit",
      "reference_type": "top_up",
      "reference_id": null,
      "invoice_id": null,
      "description": "Monthly prepayment",
      "metadata": {
        "payment_id": "pay_123",
        "source": "stripe"
      },
      "created_at": "2025-01-15T10:00:00Z"
    },
    {
      "id": "ledger_002",
      "balance_id": "bal_abc123",
      "amount": "-0.025",
      "type": "debit",
      "reference_type": "usage_tick",
      "reference_id": "tick_20250115_100000",
      "invoice_id": null,
      "description": "Usage tick: 10 seconds",
      "metadata": null,
      "created_at": "2025-01-15T10:00:10Z"
    },
    {
      "id": "ledger_003",
      "balance_id": "bal_abc123",
      "amount": "-24.475",
      "type": "debit",
      "reference_type": "settlement",
      "reference_id": "sess_xyz789",
      "invoice_id": "inv_abc123",
      "description": "Session settlement",
      "metadata": null,
      "created_at": "2025-01-15T11:30:00Z"
    }
  ],
  "total": 3
}

Ledger Entry Types

  • credit - Top-up (positive amount)
  • debit - Usage tick or settlement (negative amount)

Reference Types

  • top_up - Balance top-up
  • usage_tick - Usage tick deduction
  • settlement - Session settlement

Integration with External Payments

For external payment systems (Stripe, PayPal, etc.), use the manual top-up endpoint after receiving payment confirmation:

Example: Stripe Integration

import stripe
from orvion import OrvionClient

stripe.api_key = "your_stripe_key"
orvion = OrvionClient(api_key="your_orvion_key")

async def handle_stripe_payment_success(payment_intent_id: str, customer_ref: str):
    # Get payment details from Stripe
    payment_intent = stripe.PaymentIntent.retrieve(payment_intent_id)
    
    # Top up balance in Orvion (manual credit)
    balance = await orvion.metered_billing.top_up_balance(
        customer_ref=customer_ref,
        currency="USD",
        amount=str(payment_intent.amount / 100),  # Convert cents to dollars
        description=f"Stripe payment {payment_intent_id}",
        metadata={
            "stripe_payment_intent_id": payment_intent_id,
            "stripe_customer_id": payment_intent.customer,
            "payment_method": "stripe"
        }
    )
    
    return balance

Note: For Orvion's native payment flow, use top-up-with-payment instead - it handles everything automatically!


Low Balance Alerts

Set a low_balance_threshold to monitor when balances are running low. You can check balances periodically and send alerts:

balances = await orvion.metered_billing.list_balances()

for balance in balances:
    if balance.low_balance_threshold:
        if float(balance.available_amount) < float(balance.low_balance_threshold):
            # Send alert to customer
            send_low_balance_alert(balance.customer_ref, balance.available_amount)

Important Notes

Currency Matching

Balances are per-currency. A customer can have separate balances for USD, EUR, etc. Usage sessions must match the balance currency.

Automatic Deduction

When usage ticks are reported, the cost is automatically deducted from the customer's balance. No manual intervention needed.

Negative Balances

Balances can go negative if usage exceeds available funds. You can:

  • Allow negative balances and settle later
  • Stop sessions when balance is insufficient
  • Top up balance to cover usage

Next Steps