Skip to main content

Agent Cloud Billing

Billing service for cloud agent resource consumption. Extends the EndpointCredits credit system with per-agent, per-customer metering across six resource types.

Refs #1981, #1983


Overview​

Agent Cloud Billing tracks every resource an agent consumes on behalf of a customer. Each usage event is recorded in the agent_resource_usage table with a calculated credit cost. Credits are deducted from the customer's balance in credit_transactions.

Agent performs work
|
v
record_usage() called with resource_type + quantity
|
v
Credit cost calculated (quantity * credits_per_unit)
|
v
AgentResourceUsage row inserted
|
v
deduct_credits() removes credits from customer balance

Usage recording and credit deduction are two independent steps, and a given call path may do one without the other. For A2A specifically, the router records the usage event via record_usage() and performs its own deduction against zerodb_projects.credit_balance — it does not call this service's deduct_credits() (which writes to credit_transactions). So an agent_resource_usage row does not by itself imply a matching credit_transactions row.


Resource Types and Pricing​

Six resource types are metered. Each has a fixed credit-per-unit rate.

Resource TypeCredits per UnitUnitDescription
compute2secondsAgent CPU execution time
memory_ops5operationsZeroMemory read/write operations
vector_search8queriesVector similarity search queries
storage0.001bytesObject/file storage consumption
a2a3messagesAgent-to-agent communication messages
postgresql20queriesDedicated PostgreSQL queries

Which resource types are metered through this service today​

The six types above are not all written by the same code path, and the rates below are not all the rates actually applied. Read this before assuming a credit_cost value in agent_resource_usage came from the table above.

Resource TypeWritten byUses the rates above?
a2aAgentA2ARouterService.route_direct(), per message, in real timeYes
computeaggregate_agent_usage Celery task, every 5 minYes
memory_opsaggregate_agent_usage Celery task, every 5 minYes
vector_searchNot yet metered -- see note belowN/A
storageaggregate_agent_usage Celery task, every 5 minYes
postgresqlaggregate_agent_usage Celery task, every 5 minYes

a2a is recorded through AgentCloudBillingService.record_usage(), synchronously at the moment the message is routed (Refs #9309). Four of the remaining five (compute, memory_ops, storage, postgresql) are written by the aggregate_agent_usage Celery task (app/tasks/agent_usage_aggregation.py) every 5 minutes.

vector_search is not currently metered (core#9332). It has a rate in the pricing table above and is accepted as a valid resource_type by calculate_credit_cost() / record_usage(), but no code path anywhere in the backend actually calls either with resource_type="vector_search" today -- not the Celery task (its TRACE_TYPE_TO_RESOURCE map has no vector_search entry, and no agent_traces row is ever written with that trace type either), and not any of the platform's vector-search services (hybrid_search_service.py, optimized_vector_search.py, semantic_search.py, the agent framework's vector_search_tool.py). Vector searches an agent performs today are not billed against the customer's credit balance. Treat the vector_search row in the pricing table as the rate that will apply once metering is wired up, not as a rate currently being charged.

Resolved (Refs #9321): the Celery task used to declare its own, independent CREDIT_PRICING table with different rates AND different units (compute priced per-60-seconds instead of per-second; storage priced per-MB instead of per-byte, while the stored row's unit column claimed "bytes" either way). CLOUD_PRICING above is now the single source of truth — the Celery task imports it directly and converts its own source data (container-seconds, trace duration, storage MB) into the units CLOUD_PRICING expects before pricing. SUM(credit_cost) across agent_resource_usage is now safe to compute: every row, from either code path, is priced on the same scale.

Echo developer markup is now applied inside record_usage() itself, so it covers every resource type uniformly — previously only the Celery path applied markup (via its own _apply_markup_and_deduct), so a developer earned nothing on their agents' a2a traffic specifically. The markup breakdown (base cost, developer earnings, platform fee, total) is stored under metadata.markup on every row.

Cost Examples​

OperationQuantityCalculationCredit Cost
60 seconds of compute6060 * 2120.000
10 memory operations1010 * 550.000
5 vector searches (not yet billed -- see above)55 * 840.000
1 MB storage (1,048,576 bytes)10485761048576 * 0.0011048.576
25 agent-to-agent messages2525 * 375.000
3 PostgreSQL queries33 * 2060.000

All credit costs are calculated as Decimal values and quantized to 3 decimal places. The vector-search row is the formula calculate_credit_cost() would apply if called with resource_type="vector_search" -- as the note above explains, nothing calls it with that value today, so this row is illustrative only, not a cost customers are actually charged.


AgentResourceUsage Model​

The agent_resource_usage table stores every metered event.

ColumnTypeDescription
idUUIDPrimary key
agent_idVARCHAR(255)Agent identifier (indexed)
customer_idUUIDForeign key to users.id (indexed)
resource_typeVARCHAR(32)One of the six resource types
quantityNUMERICAmount of resource consumed
unitVARCHAR(32)Unit of measurement (seconds, operations, etc.)
credit_costNUMERICCalculated credit cost for this usage
period_startDATETIMEStart of the metering period
period_endDATETIMEEnd of the metering period
metadataJSONOptional additional context
created_atDATETIMERecord creation timestamp

Service API​

AgentCloudBillingService​

Instantiated with a SQLAlchemy Session. All methods operate within the caller's transaction.

from app.services.agent_cloud_billing_service import AgentCloudBillingService

service = AgentCloudBillingService(db=session)

calculate_credit_cost(resource_type, quantity)​

Calculate the credit cost for a given resource type and quantity without recording usage.

Parameters:

  • resource_type (str) -- One of: compute, memory_ops, vector_search, storage, a2a, postgresql
  • quantity (Decimal) -- Amount of resource consumed

Returns: Decimal -- credit cost quantized to 3 decimal places

Raises: ValueError if the resource type is unknown

cost = service.calculate_credit_cost("vector_search", Decimal("15"))
## cost = Decimal("120.000")

record_usage(agent_id, customer_id, resource_type, quantity, period_start, period_end, metadata=None)​

Record a resource usage event and calculate its credit cost. Inserts a row into agent_resource_usage and flushes (but does not commit -- the caller controls the transaction).

Parameters:

  • agent_id (str) -- Agent identifier
  • customer_id (str) -- Customer UUID as string
  • resource_type (str) -- One of the six valid resource types
  • quantity (float) -- Amount consumed (must be positive)
  • period_start (datetime) -- Start of metering period
  • period_end (datetime) -- End of metering period (must be after start)
  • metadata (dict, optional) -- Additional context

Returns: Dictionary with the recorded usage details:

{
"id": "uuid-string",
"agent_id": "aurora",
"customer_id": "customer-uuid",
"resource_type": "compute",
"quantity": 120.0,
"unit": "seconds",
"credit_cost": 240.0,
"period_start": "2026-06-01T00:00:00",
"period_end": "2026-06-01T01:00:00"
}

Raises:

  • ValueError if resource_type is unknown
  • ValueError if period_end is not after period_start
  • ValueError if quantity is not positive

deduct_credits(customer_id, credits, description)​

Deduct credits from a customer's balance via the credit_transactions table. Checks the current balance before deducting and returns False if insufficient.

Parameters:

  • customer_id (str) -- Customer UUID as string
  • credits (float) -- Number of credits to deduct (positive value)
  • description (str) -- Reason for the deduction

Returns: True if deduction succeeded, False if insufficient balance

Raises: ValueError if credits is not positive

success = service.deduct_credits(
customer_id="customer-uuid",
credits=240.0,
description="Agent aurora compute: 120s execution"
)

get_agent_usage_summary(agent_id, period_start, period_end)​

Retrieve aggregated usage for a specific agent over a time period, grouped by resource type.

Not exposed over HTTP

get_agent_usage_summary() and get_customer_cloud_spend() are service-layer methods with no REST endpoint of their own. To read per-agent usage over HTTP, use GET /api/v1/cloud/observability/costs/{agent_id} — it queries the same agent_resource_usage table and returns the same per-resource-type breakdown. There is currently no customer-level (all-agents) equivalent. Tracked in core#9320.

Note also that GET /api/v1/billing/agent-usage despite its name does not read agent_resource_usage — it reads agent_traces and estimates cost from token counts, which is a different data source and a different number.

Parameters:

  • agent_id (str) -- Agent identifier
  • period_start (datetime) -- Start of query period
  • period_end (datetime) -- End of query period

Returns:

{
"agent_id": "aurora",
"period_start": "2026-06-01T00:00:00",
"period_end": "2026-06-01T23:59:59",
"total_credit_cost": 510.0,
"total_records": 42,
"by_resource_type": {
"compute": {
"total_quantity": 300.0,
"total_credit_cost": 600.0,
"unit": "seconds",
"record_count": 15
},
"vector_search": {
"total_quantity": 50.0,
"total_credit_cost": 400.0,
"unit": "queries",
"record_count": 12
}
}
}

get_customer_cloud_spend(customer_id, period_start, period_end)​

Retrieve total cloud spend for a customer across all agents, grouped by agent and resource type.

Parameters:

  • customer_id (str) -- Customer UUID as string
  • period_start (datetime) -- Start of query period
  • period_end (datetime) -- End of query period

Returns:

{
"customer_id": "customer-uuid",
"period_start": "2026-06-01T00:00:00",
"period_end": "2026-06-30T23:59:59",
"total_credit_cost": 1250.0,
"total_records": 87,
"by_agent": {
"aurora": {
"total_credit_cost": 800.0,
"record_count": 52,
"by_resource_type": {
"compute": {
"total_quantity": 200.0,
"total_credit_cost": 400.0,
"unit": "seconds",
"record_count": 20
}
}
},
"sage": {
"total_credit_cost": 450.0,
"record_count": 35,
"by_resource_type": {}
}
}
}

Budget Alerts and Cost Management​

Monitoring Spend​

Use get_customer_cloud_spend to monitor a customer's total spend across all agents. Compare against budget thresholds to trigger alerts:

spend = service.get_customer_cloud_spend(
customer_id="customer-uuid",
period_start=month_start,
period_end=month_end,
)

if spend["total_credit_cost"] > budget_threshold:
# Trigger budget alert notification
pass

Pre-flight Balance Checks​

Before executing expensive operations, check whether the customer has sufficient credits:

estimated_cost = service.calculate_credit_cost("postgresql", Decimal("10"))
## Check balance before proceeding
can_deduct = service.deduct_credits(
customer_id="customer-uuid",
credits=float(estimated_cost),
description="Agent sage: 10 PostgreSQL queries"
)
if not can_deduct:
# Reject the operation or notify the customer
pass

Per-Agent Cost Attribution​

Use get_agent_usage_summary to attribute costs to individual agents. This enables:

  • Identifying which agents consume the most resources
  • Optimizing expensive agents (e.g., reducing unnecessary vector searches)
  • Billing breakdowns for customers running multiple agents

Data Lake Integration​

Agent resource usage is exported to the data lake as the platform/agent_resource_usage partition. This enables historical analysis and trend detection via the lake query endpoint:

SELECT agent_id, resource_type, SUM(credit_cost) as total_cost
FROM platform_agent_resource_usage
GROUP BY agent_id, resource_type
ORDER BY total_cost DESC

See docs/api/DATA_LAKE_API.md for lake query details.


Pydantic Schemas​

AgentResourceUsageCreate​

Request schema for creating a usage record.

FieldTypeRequiredConstraints
agent_idstryes1-255 chars
customer_idUUIDyes
resource_typeenumyesOne of the 6 resource types
quantityDecimalyesMust be > 0
unitenumyesMatching unit for type
credit_costDecimalyesMust be >= 0
period_startdatetimeyes
period_enddatetimeyesMust be after period_start
metadatadictnoDefault: empty dict

AgentResourceUsageResponse​

Response schema returned by API endpoints.

Includes all fields from AgentResourceUsageCreate plus:

FieldTypeDescription
idUUIDRecord primary key
created_atdatetimeCreation timestamp

AgentResourceUsageSummary​

Aggregated summary schema.

FieldTypeDescription
agent_idstrAgent identifier
resource_typeenumResource type
total_quantityDecimalSum of quantity
total_credit_costDecimalSum of credit cost
unitenumUnit of measurement
period_startdatetimeSummary period start
period_enddatetimeSummary period end
record_countintNumber of usage records

Spending Caps (Budgets)​

Separate from credit metering and x402 wallets, you can set a USD-denominated spending cap that gates real-time A2A operations — POST /cloud/a2a/{agent_id}/message and POST /cloud/a2a/route. If the cap is exceeded, the operation is blocked with 402 Payment Required before it executes, not after.

This is the mechanism to understand if you're building agents that call /cloud/a2a/* and get an unexpected 402 — a budget gate is almost certainly why, not a credit-balance issue.

agent_id for an A2A budget is YOUR user id, not a Registry agent_id

A2A spend is attributed to the authenticated caller, not the Registry agent being messaged — both /cloud/a2a/{agent_id}/message and /cloud/a2a/route derive the budget's identity key server-side as user-<your user id>. Creating a budget with "agent_id": "agent-xxxxxxxxxxxx" (a real agent you registered via POST /cloud/agents/register) has no effect whatsoever on A2A spend — the gate will never check that row. To actually cap your A2A spend, set "agent_id": "user-<your own user id>" when creating the budget (see Refs #9302).

Base path: /api/v1/billing

POST /budgets​

Create a spending cap. For an A2A spending cap specifically, agent_id must be user-<your user id> — see the warning above.

{
"agent_id": "user-3433680a-bfbe-468c-8bd7-42f83ee62ae5",
"limit_usd": 50.00,
"period": "month",
"max_task_cost_usd": 2.00,
"auto_pause": true
}
FieldTypeRequiredDescription
agent_idstringyesIdentity key this budget applies to. For A2A gating, this is user-<your user id> (see warning above), not a Registry agent_id.
limit_usdfloatyesTotal spend allowed per period (> 0, ≤ 1,000,000)
periodstringyesday, week, or month
max_task_cost_usdfloatnoReject any single operation costing more than this
auto_pauseboolno (default false)Automatically pause the agent when the period limit is hit

Response (201 Created): a BudgetItem — same fields plus id, current_spend_usd (starts at 0), created_at, updated_at.

GET /budgets​

List budgets, optionally filtered by agent_id query param.

How the gate works​

Every budgeted operation checks two things, in order:

  1. Per-task cap: if max_task_cost_usd is set and this operation's estimated cost exceeds it, reject immediately regardless of remaining period budget.
  2. Period cap: if current_spend_usd + estimated_cost > limit_usd, reject. If auto_pause is enabled, the agent is also paused (its registration is suspended) so subsequent calls fail fast instead of repeatedly hitting the same budget check.

An agent with no budget row at all has no spending cap — budgets are opt-in, not a default restriction.

Agent Wallets (x402)​

In addition to credit-based billing, agents can hold USDC wallets for x402 micropayments. Wallets provide per-agent spending isolation with configurable limits. Refs #5008

Wallet Spending Guards​

Each wallet enforces three layers of protection:

GuardDefaultDescription
Single payment cap$10.00Maximum amount per individual transaction
Period spending limit$100.00/monthMaximum total spend per billing period
Balance check—Transaction rejected if wallet balance insufficient

Wallet Endpoints​

MethodPathDescription
POST/api/v1/cloud/x402/walletsCreate per-agent wallet
GET/api/v1/cloud/x402/wallets/{agent_id}Get wallet balance and limits
POST/api/v1/cloud/x402/wallets/{agent_id}/fundAdd funds to wallet
POST/api/v1/cloud/x402/wallets/{agent_id}/withdrawWithdraw from wallet
PUT/api/v1/cloud/x402/wallets/{agent_id}/limitsUpdate spending limits
GET/api/v1/cloud/x402/wallets/{agent_id}/transactionsTransaction history

Integration with Credit Billing​

There are three independent billing/spend systems, each covering a different concern:

  • Credits (AgentCloudBillingService) meter platform resource consumption (compute, vectors, storage, etc.) and deduct from the customer's credit balance.
  • Budgets (/billing/budgets, above) are a real-time USD spending cap that gates operations like A2A messages before they execute — independent of credit balance.
  • Wallets hold USDC for tool marketplace payments via x402.

An agent can consume platform credits for infrastructure, be gated by a USD budget cap on A2A traffic, AND spend USDC for external tool calls — all at once, as separate concerns. Use get_customer_cloud_spend for credit costs, GET /billing/budgets for spending-cap status, and the wallet transaction history for x402 payment costs.

Full wallet API documentation: Agent402 API — Agent Wallets


Key Source Files​

FilePurpose
src/backend/app/services/agent_cloud_billing_service.pyCore billing service with pricing and metering
src/backend/app/models/agent_resource_usage.pySQLAlchemy model for agent_resource_usage table
src/backend/app/schemas/agent_resource_usage.pyPydantic schemas (ResourceType, ResourceUnit, Create/Response/Summary)
src/backend/app/services/agent_wallet_service.pyPer-agent USDC wallet service with spending guards
src/backend/app/services/x402_revenue_share_service.pyMarketplace 90/5/5 revenue split service