# How Bella OpenAPI Calculates and Tracks API Usage Costs per Request

> Discover how Bella OpenAPI calculates API usage costs per request using its unique three-stage pipeline. Learn about pricing formulas, log handling, and delta aggregation for accurate billing.

- Repository: [Ke Technologies/bella-openapi](https://github.com/lianjiatech/bella-openapi)
- Tags: how-to-guide
- Published: 2026-03-06

---

**Bella OpenAPI determines API usage costs per request through a three-stage pipeline that combines endpoint-specific price formulas, asynchronous log handling with optional batch discounts, and periodic delta aggregation to monthly billing records.**

The lianjiatech/bella-openapi project implements a granular cost tracking system that monetizes every API call based on provider-specific pricing models. Understanding how Bella OpenAPI calculates API usage costs per request reveals a sophisticated architecture that separates calculation logic from persistence concerns while supporting diverse billing models including token-based, time-based, and per-image pricing.

## The Three-Stage Cost Tracking Architecture

Bella OpenAPI processes every billable request through a tightly-coupled pipeline involving calculation, logging, and accumulation. Each stage operates asynchronously to ensure that cost tracking never blocks the critical path of API responses.

### Stage 1: Endpoint-Specific Cost Calculation with CostCalculator

The calculation logic resides in [`CostCalculator.java`](https://github.com/lianjiatech/bella-openapi/blob/main/CostCalculator.java) located at [`api/server/src/main/java/com/ke/bella/openapi/protocol/cost/CostCalculator.java`](https://github.com/lianjiatech/bella-openapi/blob/main/api/server/src/main/java/com/ke/bella/openapi/protocol/cost/CostCalculator.java). This class serves as a central dispatcher that maps URL patterns to dedicated `EndpointCostCalculator` implementations via the `CostCalculators` enum.

Each calculator knows the exact formula for its service type:
- **Token-based pricing** for chat completions (input/output token rates)
- **Time-based pricing** for real-time audio (per-hour or per-second rates)
- **Per-image pricing** for image generation endpoints

The `CostCalculator.calculate(endpoint, priceInfo, usage)` method accepts three parameters: the request endpoint, a JSON string containing the provider's price model, and a structured usage object (tokens, seconds, or image counts). It returns a `BigDecimal` representing the exact cost in the platform's currency.

```java
// Direct cost calculation for a completion request
String endpoint = "/v1/chat/completions";
String priceInfoJson = "{\"range\":[{\"input\":0.0005,\"output\":0.001,\"minTokens\":0,\"maxTokens\":4096}]}";
CompletionResponse.TokenUsage usage = new CompletionResponse.TokenUsage(120, 30);
BigDecimal cost = CostCalculator.calculate(endpoint, priceInfoJson, usage);
// → cost ≈ (0.0005 × 0.12) + (0.001 × 0.03) = 0.00006

```

### Stage 2: Asynchronous Log Processing and Batch Discounts

Once a request completes, the interceptor creates a `LogEvent` that flows to [`CostLogHandler.java`](https://github.com/lianjiatech/bella-openapi/blob/main/CostLogHandler.java) at [`api/server/src/main/java/com/ke/bella/openapi/protocol/log/CostLogHandler.java`](https://github.com/lianjiatech/bella-openapi/blob/main/api/server/src/main/java/com/ke/bella/openapi/protocol/log/CostLogHandler.java). The `onEvent` method processes these logs asynchronously.

For inner logs (generated by internal provider adapters), the handler invokes the calculator and applies optional batch discounts. If the `priceInfo` JSON contains a `"batchDiscount"` field, the handler multiplies the calculated cost by this discount factor before storage.

```java
// Simplified log handler implementation
public void onEvent(LogEvent ev, ...) {
    if (ev.isInnerLog()) {
        BigDecimal cost = CostCalculator.calculate(
                ev.getEndpoint(),
                ev.getPriceInfo(),
                ev.getUsage());

        // Apply batch discount if present
        if (ev.isBatch()) {
            Map<String,Object> pi = JacksonUtils.deserialize(ev.getPriceInfo(), Map.class);
            double discount = MapUtils.getDoubleValue(pi, "batchDiscount", 1.0);
            cost = cost.multiply(BigDecimal.valueOf(discount));
        }
        ev.setCost(cost);
        costCounter.delta(ev.getAkCode(), cost);
    }
}

```

### Stage 3: Delta Aggregation and Periodic Persistence

The [`CostCounter.java`](https://github.com/lianjiatech/bella-openapi/blob/main/CostCounter.java) class at [`api/server/src/main/java/com/ke/bella/openapi/protocol/cost/CostCounter.java`](https://github.com/lianjiatech/bella-openapi/blob/main/api/server/src/main/java/com/ke/bella/openapi/protocol/cost/CostCounter.java) maintains an in-memory `ConcurrentHashMap<String, AtomicReference<BigDecimal>>` where each key represents an API key code. This design minimizes database write pressure by aggregating costs in memory before batch persistence.

Every 60 seconds, a scheduled `flush()` method reads the map, removes entries atomically, and delegates to `ApikeyService.recordCost`. The default `costRecorder` implementation updates the monthly cost table through `ApikeyCostRepo`.

```java
// Scheduled flushing mechanism
@Scheduled(fixedRate = 60000)
public synchronized void flush() {
    if (!costCache.isEmpty()) {
        String month = DateTimeUtils.getCurrentMonth();
        for (String apiKey : costCache.keySet()) {
            AtomicReference<BigDecimal> amountRef = costCache.remove(apiKey);
            costRecorder.recordCost(apiKey, month, amountRef.get());
        }
    }
}

```

## Database Persistence and Caching Strategy

The [`ApikeyService.java`](https://github.com/lianjiatech/bella-openapi/blob/main/ApikeyService.java) file at [`api/server/src/main/java/com/ke/bella/openapi/service/ApikeyService.java`](https://github.com/lianjiatech/bella-openapi/blob/main/api/server/src/main/java/com/ke/bella/openapi/service/ApikeyService.java) contains the `recordCost` method that persists accumulated deltas. This method operates within a transactional boundary and updates the `APIKEY_MONTH_COST` table via [`ApikeyCostRepo.java`](https://github.com/lianjiatech/bella-openapi/blob/main/ApikeyCostRepo.java).

The implementation uses an upsert pattern: it queries for an existing monthly record, inserts a new row if absent, then increments the `amount` column by the delta value. The `@CacheUpdate` annotation ensures that cached cost reads remain consistent after each update.

```java
@Transactional
@CacheUpdate(name = "apikey:cost:month:", key = "#akCode + ':' + #month", value = "#result")
public BigDecimal recordCost(String akCode, String month, BigDecimal cost) {
    BigDecimal current = apikeyCostRepo.queryCost(akCode, month);
    if (current == null) {
        apikeyCostRepo.insert(akCode, month);
    }
    apikeyCostRepo.increment(akCode, month, cost);
    return apikeyCostRepo.queryCost(akCode, month);
}

```

For non-standard pricing scenarios, the `CostScripFetcher` (referenced in [`CostLogHandler.java`](https://github.com/lianjiatech/bella-openapi/blob/main/CostLogHandler.java) at line 77) supports custom Groovy scripts that override the default calculation logic, providing a fallback path for complex provider agreements.

## Summary

- **Endpoint-specific calculation**: The `CostCalculator` class dispatches to specialized calculators based on URL patterns, supporting token-based, time-based, and per-image pricing models.
- **Asynchronous cost logging**: `CostLogHandler` processes logs outside the request path, applies batch discounts from JSON configuration, and stores costs in `LogEvent` objects.
- **In-memory aggregation**: `CostCounter` uses a `ConcurrentHashMap` of `AtomicReference<BigDecimal>` to batch deltas per API key, flushing every 60 seconds to reduce database load.
- **Transactional persistence**: `ApikeyService.recordCost` updates the `APIKEY_MONTH_COST` table atomically while maintaining cache consistency via JOOQ-based SQL operations in `ApikeyCostRepo`.
- **Extensibility**: The system supports custom Groovy scripts through `CostScripFetcher` for providers requiring non-standard cost calculations.

## Frequently Asked Questions

### How does Bella OpenAPI support different pricing models for various AI services?

Bella OpenAPI uses the `CostCalculators` enum to map URL patterns to specific `EndpointCostCalculator` implementations. Each calculator implements distinct formulas: token-based for LLM completions, hourly rates for real-time audio, and per-image counts for generation endpoints. This architecture allows the platform to accommodate any provider's pricing structure through the `CostCalculator.calculate` method signature.

### What role do batch discounts play in the cost calculation pipeline?

When processing batch requests, the `CostLogHandler` inspects the `priceInfo` JSON for a `"batchDiscount"` field. If present, the handler multiplies the calculated cost by this discount factor (defaulting to 1.0) before storing it in the log and forwarding to the counter. This enables volume-based pricing reductions without modifying the core calculation logic.

### How frequently are API usage costs persisted to the database?

Costs accumulate in memory within `CostCounter` and flush to the database every 60 seconds via a scheduled task. The `flush()` method atomically removes entries from the concurrent map and delegates to `ApikeyService.recordCost`, ensuring that monthly billing records receive batched updates rather than per-request database writes.

### Can developers implement custom pricing logic for specific endpoints?

Yes, the system provides an extension point through `CostScripFetcher` in [`CostLogHandler.java`](https://github.com/lianjiatech/bella-openapi/blob/main/CostLogHandler.java). This component allows operators to upload custom Groovy scripts that override the default `CostCalculator` logic for specific endpoints. When a script is configured for an endpoint pattern, the handler executes the custom calculation instead of the standard enum-mapped calculator.