How Bella OpenAPI Calculates and Tracks API Usage Costs per Request

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 located at 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.

// 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 at 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.

// 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 class at 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.

// 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 file at 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.

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.

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

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →