Cost Controls API
The Cost Controls API provides budget management and LLM usage tracking capabilities, enabling organizations to set spending limits, receive alerts, and enforce budget policies on LLM usage.
All Cost Controls endpoints are available through the official SDKs:
Base URL: http://localhost:8081 (Orchestrator)
Authentication
All endpoints require:
Authorization: Basic base64(clientId:clientSecret)headerContent-Type: application/jsonheader (for POST/PUT requests)
Optional:
X-Org-IDheader -- Scopes budgets and usage to a specific tenant
Overview
Cost controls in AxonFlow are governance policies. Budget limits are enforced at multiple scopes:
| Scope | Description |
|---|---|
organization | Organization-wide budget |
team | Budget for a specific team |
agent | Budget for an individual agent |
workflow | Budget for a workflow |
user | Budget for a specific user |
Budget Management
Create Budget
Create a new budget with spending limits.
POST /api/v1/budgets
Headers:
Content-Type: application/json
X-Org-ID: your-org-id
X-Client-Secret: your-secret
Request Body:
{
"id": "monthly-budget",
"name": "Monthly Production Budget",
"scope": "organization",
"limit_usd": 1000.00,
"period": "monthly",
"on_exceed": "warn",
"alert_thresholds": [50, 80, 100]
}
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
id | string | Yes | - | Unique budget identifier |
name | string | Yes | - | Human-readable budget name |
scope | string | Yes | - | Budget scope: organization, team, agent, workflow, user |
scope_id | string | No | null | ID of the scoped entity (required for team/agent/workflow/user scopes) |
limit_usd | number | Yes | - | Budget limit in USD |
period | string | Yes | - | Budget period: daily, weekly, monthly, quarterly, yearly |
on_exceed | string | Yes | - | Action when exceeded: warn, block, downgrade |
alert_thresholds | number[] | No | [] | Percentage thresholds for alerts (e.g., [50, 80, 100]) |
enabled | boolean | No | true | Whether budget enforcement is active |
Response (201 Created):
{
"id": "monthly-budget",
"name": "Monthly Production Budget",
"scope": "organization",
"limit_usd": 1000.00,
"period": "monthly",
"on_exceed": "warn",
"alert_thresholds": [50, 80, 100],
"enabled": true,
"created_at": "2026-01-04T00:00:00Z",
"updated_at": "2026-01-04T00:00:00Z"
}
List Budgets
List all budgets for the organization.
GET /api/v1/budgets
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
scope | string | Filter by scope |
limit | integer | Maximum results (default: 50) |
offset | integer | Pagination offset (default: 0) |
Response:
{
"budgets": [
{
"id": "monthly-budget",
"name": "Monthly Production Budget",
"scope": "organization",
"limit_usd": 1000.00,
"period": "monthly",
"on_exceed": "warn",
"alert_thresholds": [50, 80, 100],
"enabled": true
}
],
"total": 1
}
Get Budget
Get a specific budget by ID.
GET /api/v1/budgets/{id}
Update Budget
Update an existing budget configuration.
PUT /api/v1/budgets/{id}
Request Body:
{
"name": "Updated Budget Name",
"limit_usd": 2000.00,
"on_exceed": "block",
"alert_thresholds": [25, 50, 75, 100]
}
All fields are optional. Only provided fields are updated.
Delete Budget
Delete a budget by ID.
DELETE /api/v1/budgets/{id}
Response: 204 No Content
Budget Status & Alerts
Get Budget Status
Get real-time status of a budget including current usage.
GET /api/v1/budgets/{id}/status
Response:
{
"budget": {
"id": "monthly-budget",
"name": "Monthly Production Budget",
"limit_usd": 1000.00,
"period": "monthly"
},
"used_usd": 450.25,
"remaining_usd": 549.75,
"percentage": 45.025,
"period_start": "2026-01-01T00:00:00Z",
"period_end": "2026-02-01T00:00:00Z",
"is_exceeded": false,
"is_blocked": false
}
Get Budget Alerts
Get alerts triggered for a specific budget.
GET /api/v1/budgets/{id}/alerts
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
limit | integer | Maximum alerts to return (default: 50) |
Response:
{
"alerts": [
{
"id": "alert-123",
"budget_id": "monthly-budget",
"alert_type": "threshold",
"threshold": 50,
"percentage_reached": 52.3,
"amount_usd": 523.00,
"message": "Budget 'Monthly Production Budget' has reached 50% threshold",
"created_at": "2026-01-15T10:30:00Z"
}
],
"count": 1
}
Check Budget (Pre-flight)
Check if a request should be allowed based on budget constraints. Use this before making LLM requests.
POST /api/v1/budgets/check
Request Body:
{
"org_id": "your-org-id",
"team_id": "engineering",
"agent_id": "support-bot"
}
Response (Allowed):
{
"allowed": true
}
Response (Blocked):
{
"allowed": false,
"action": "block",
"budget_id": "team-budget",
"budget_name": "Engineering Team Budget",
"used_usd": 520.00,
"limit_usd": 500.00,
"percentage": 104.0,
"message": "Budget 'Engineering Team Budget' exceeded - requests blocked"
}
Usage Tracking
Get Usage Summary
Get aggregated LLM usage for the current period.
GET /api/v1/usage
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
period | string | Period: daily, weekly, monthly, quarterly, yearly |
Response:
{
"period": "monthly",
"period_start": "2026-01-01T00:00:00Z",
"period_end": "2026-02-01T00:00:00Z",
"total_cost_usd": 1523.45,
"total_requests": 45230,
"total_tokens_in": 12500000,
"total_tokens_out": 8750000,
"by_provider": {
"openai": {
"cost_usd": 890.00,
"requests": 25000,
"tokens_in": 7500000,
"tokens_out": 5000000
},
"anthropic": {
"cost_usd": 633.45,
"requests": 20230,
"tokens_in": 5000000,
"tokens_out": 3750000
}
}
}
Get Usage Breakdown
Get usage broken down by a specific dimension.
GET /api/v1/usage/breakdown
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
group_by | string | Dimension: provider, model, agent, team, workflow |
period | string | Period: daily, weekly, monthly, quarterly, yearly |
Response:
{
"group_by": "model",
"period": "monthly",
"items": [
{
"name": "gpt-4o",
"cost_usd": 650.00,
"requests": 8500,
"percentage": 42.7
},
{
"name": "claude-sonnet-4",
"cost_usd": 480.00,
"requests": 12000,
"percentage": 31.5
},
{
"name": "gpt-4o-mini",
"cost_usd": 120.00,
"requests": 18000,
"percentage": 7.9
}
],
"total_cost_usd": 1523.45
}
List Usage Records
Get detailed usage records with filtering.
GET /api/v1/usage/records
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
limit | integer | Maximum records (default: 50) |
offset | integer | Pagination offset |
provider | string | Filter by provider |
model | string | Filter by model |
start_time | string | Filter by start time (ISO 8601) |
end_time | string | Filter by end time (ISO 8601) |
Response:
{
"records": [
{
"id": "usage-123",
"timestamp": "2026-01-04T12:30:00Z",
"provider": "openai",
"model": "gpt-4o",
"tokens_in": 1500,
"tokens_out": 800,
"cost_usd": 0.045,
"agent_id": "support-bot",
"team_id": "engineering",
"request_id": "req-abc123"
}
],
"total": 1
}
Pricing Information
Get Pricing
Get model pricing information.
GET /api/v1/pricing
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
provider | string | Filter by provider |
model | string | Filter by model |
Response:
{
"pricing": [
{
"provider": "openai",
"model": "gpt-4o",
"input_price_per_1k": 0.01,
"output_price_per_1k": 0.03,
"currency": "USD",
"updated_at": "2026-01-01T00:00:00Z"
},
{
"provider": "anthropic",
"model": "claude-sonnet-4",
"input_price_per_1k": 0.003,
"output_price_per_1k": 0.015,
"currency": "USD",
"updated_at": "2026-01-01T00:00:00Z"
}
]
}
Cost Estimation
Added in v4.3.0.
Estimate the cost of a workflow plan before execution.
Estimate Plan Cost
Estimate the cost of a plan definition without creating or executing it.
POST /api/v1/plans/estimate
Headers:
Content-Type: application/json
X-Org-ID: your-org-id
X-Client-Secret: your-secret
Request Body:
{
"provider": "openai",
"model": "gpt-4",
"steps": [
{
"name": "analyze",
"type": "llm_call",
"estimated_tokens_in": 1000,
"estimated_tokens_out": 500
}
]
}
| Field | Type | Required | Description |
|---|---|---|---|
provider | string | Yes | LLM provider (e.g., openai, anthropic) |
model | string | Yes | Model identifier (e.g., gpt-4, claude-sonnet-4) |
steps | array | Yes | List of plan steps to estimate |
steps[].name | string | Yes | Step name |
steps[].type | string | Yes | Step type: llm_call, tool_call, connector_call |
steps[].estimated_tokens_in | integer | Yes | Estimated input tokens for this step |
steps[].estimated_tokens_out | integer | Yes | Estimated output tokens for this step |
Response (200 OK):
{
"estimated_cost_usd": 0.045,
"currency": "USD",
"breakdown": [
{
"step": "analyze",
"type": "llm_call",
"tokens_in": 1000,
"tokens_out": 500,
"cost_usd": 0.045
}
]
}
| Field | Type | Description |
|---|---|---|
estimated_cost_usd | number | Total estimated cost in USD |
currency | string | Currency (always USD) |
breakdown | array | Per-step cost breakdown (Evaluation and Enterprise only; Community receives an empty array) |
breakdown[].step | string | Step name |
breakdown[].type | string | Step type |
breakdown[].tokens_in | integer | Estimated input tokens |
breakdown[].tokens_out | integer | Estimated output tokens |
breakdown[].cost_usd | number | Estimated cost for this step |
Tiered Access:
| Edition | Response Detail | Rate Limit |
|---|---|---|
| Community | Aggregate total only (breakdown is empty) | 10 estimates/day |
| Evaluation | Full per-step breakdown | 100 estimates/day |
| Enterprise | Full per-step breakdown | Unlimited |
Error Responses:
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | INVALID_ESTIMATE_REQUEST | Missing required fields or invalid step configuration |
| 404 | PRICING_NOT_FOUND | No pricing data available for the specified provider/model |
| 429 | RATE_LIMIT_EXCEEDED | Daily estimate limit exceeded for your edition |
Get Plan Cost Estimate
Get a cost estimate for an existing plan by ID.
GET /api/v1/plans/{id}/cost
Headers:
X-Org-ID: your-org-id
X-Client-Secret: your-secret
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Plan ID |
Response (200 OK):
{
"plan_id": "plan_abc123",
"estimated_cost_usd": 0.045,
"currency": "USD",
"breakdown": [
{
"step": "analyze",
"type": "llm_call",
"tokens_in": 1000,
"tokens_out": 500,
"cost_usd": 0.045
}
]
}
| Field | Type | Description |
|---|---|---|
plan_id | string | The plan ID |
estimated_cost_usd | number | Total estimated cost in USD |
currency | string | Currency (always USD) |
breakdown | array | Per-step cost breakdown (Evaluation and Enterprise only; Community receives an empty array) |
The breakdown array fields are identical to those in the Estimate Plan Cost response.
Error Responses:
| HTTP Status | Error Code | Description |
|---|---|---|
| 404 | PLAN_NOT_FOUND | No plan found with the specified ID |
| 429 | RATE_LIMIT_EXCEEDED | Daily estimate limit exceeded for your edition |
SDK Examples
Python
from axonflow import AxonFlow, CreateBudgetRequest, BudgetScope, BudgetPeriod, BudgetOnExceed
async with AxonFlow(endpoint="http://localhost:8080") as client:
# Create a budget
budget = await client.create_budget(CreateBudgetRequest(
id="team-budget",
name="Engineering Budget",
scope=BudgetScope.TEAM,
scope_id="engineering",
limit_usd=5000.0,
period=BudgetPeriod.MONTHLY,
on_exceed=BudgetOnExceed.WARN,
alert_thresholds=[50, 80, 100]
))
# Check budget before request
decision = await client.check_budget(BudgetCheckRequest(
team_id="engineering"
))
if decision.allowed:
# Proceed with LLM request
pass
else:
print(f"Request blocked: {decision.message}")
# Get usage summary
usage = await client.get_usage_summary(period="monthly")
print(f"Total cost: ${usage.total_cost_usd:.2f}")
TypeScript
import { AxonFlow, BudgetScope, BudgetPeriod, BudgetOnExceed } from '@axonflow/sdk';
const client = new AxonFlow({
endpoint: 'http://localhost:8080'
});
// Create a budget
const budget = await client.createBudget({
id: 'team-budget',
name: 'Engineering Budget',
scope: BudgetScope.TEAM,
scopeId: 'engineering',
limitUsd: 5000,
period: BudgetPeriod.MONTHLY,
onExceed: BudgetOnExceed.WARN,
alertThresholds: [50, 80, 100]
});
// Check budget before request
const decision = await client.checkBudget({
teamId: 'engineering'
});
if (decision.allowed) {
// Proceed with LLM request
} else {
console.log(`Request blocked: ${decision.message}`);
}
// Get usage breakdown
const breakdown = await client.getUsageBreakdown('model', 'monthly');
breakdown.items.forEach(item => {
console.log(`${item.name}: $${item.costUsd.toFixed(2)} (${item.percentage}%)`);
});
Go
package main
import (
"context"
"fmt"
axonflow "github.com/getaxonflow/axonflow-sdk-go/v3"
)
func main() {
client, _ := axonflow.NewClient(axonflow.AxonFlowConfig{
Endpoint: "http://localhost:8080",
})
defer client.Close()
ctx := context.Background()
// Create a budget
budget, _ := client.CreateBudget(ctx, axonflow.CreateBudgetRequest{
ID: "team-budget",
Name: "Engineering Budget",
Scope: "team",
ScopeID: "engineering",
LimitUSD: 5000.0,
Period: "monthly",
OnExceed: "warn",
AlertThresholds: []int{50, 80, 100},
})
fmt.Printf("Created budget: %s\n", budget.ID)
// Check budget before request
decision, _ := client.CheckBudget(ctx, axonflow.CheckBudgetRequest{
TeamID: "engineering",
})
if decision.Allowed {
// Proceed with LLM request
} else {
fmt.Printf("Request blocked: %s\n", decision.Message)
}
// Get usage summary
usage, _ := client.GetUsageSummary(ctx, "monthly")
fmt.Printf("Total cost: $%.2f\n", usage.TotalCostUSD)
}
Java
import com.getaxonflow.sdk.AxonFlow;
import com.getaxonflow.sdk.AxonFlowConfig;
import com.getaxonflow.sdk.types.costcontrols.CostControlTypes.*;
public class CostControlsExample {
public static void main(String[] args) {
AxonFlowConfig config = AxonFlowConfig.builder()
.endpoint("http://localhost:8080")
.build();
try (AxonFlow client = AxonFlow.create(config)) {
// Create a budget
Budget budget = client.createBudget(CreateBudgetRequest.builder()
.id("team-budget")
.name("Engineering Budget")
.scope(BudgetScope.TEAM)
.scopeId("engineering")
.limitUsd(5000.0)
.period(BudgetPeriod.MONTHLY)
.onExceed(BudgetOnExceed.WARN)
.alertThresholds(List.of(50, 80, 100))
.build());
// Check budget before request
BudgetDecision decision = client.checkBudget(BudgetCheckRequest.builder()
.teamId("engineering")
.build());
if (decision.isAllowed()) {
// Proceed with LLM request
} else {
System.out.println("Request blocked: " + decision.getMessage());
}
// Get usage summary
UsageSummary usage = client.getUsageSummary("monthly");
System.out.printf("Total cost: $%.2f%n", usage.getTotalCostUsd());
}
}
}
Budget Period Values
| Value | Description |
|---|---|
daily | Resets every day at midnight UTC |
weekly | Resets every Monday at midnight UTC |
monthly | Resets on the 1st of each month |
quarterly | Resets every 3 months (Jan, Apr, Jul, Oct) |
yearly | Resets on January 1st |
On Exceed Actions
| Value | Description |
|---|---|
warn | Log a warning and send alerts, but allow requests to continue |
block | Block all LLM requests until the next budget period |
downgrade | Switch to a cheaper model automatically (Enterprise only) |
Error Responses
| HTTP Status | Error Code | Description |
|---|---|---|
| 400 | INVALID_BUDGET_CONFIG | Invalid budget configuration |
| 404 | BUDGET_NOT_FOUND | Budget not found |
| 409 | BUDGET_EXISTS | Budget with this ID already exists |
| 500 | INTERNAL_ERROR | Internal server error |
Example (400):
{
"error": {
"code": "INVALID_BUDGET_CONFIG",
"message": "limit_usd must be a positive number"
}
}
See Error Codes for the complete list.
Community vs Enterprise
| Feature | Community | Enterprise |
|---|---|---|
| Usage tracking | ✅ | ✅ |
| Budget limits | ✅ | ✅ |
| Budget alerts | ✅ | ✅ |
| Pre-flight budget check | ✅ | ✅ |
| Budget hierarchy | ✅ | ✅ |
| Usage forecast | ❌ | ✅ |
| Usage export | ❌ | ✅ |
| Alert channels (Slack, email, webhook) | ❌ | ✅ |
| Auto-downgrade | ❌ | ✅ |
| Budget rollover | ❌ | ✅ |
| Cost dashboard | ❌ | ✅ |