Skip to main content
Version: Dev

Cost Analysis

Cost Analysis shows where an organization's usage and spend come from across environments, teams, members, caller API keys, models, target models, days, and cost attribution tags. Filter the usage first, then group the result by the dimension you want to compare.

You can also expand any group or the total to inspect the pricing conditions and billed items that produced its cost. Tags are optional: configure cost attribution tags when the organization needs custom dimensions such as department, subsidiary, project, or cost center.

Prerequisites​

Before starting, prepare the following:

  • Permission to read usage in the organization.
  • Recorded gateway usage in at least one environment.
  • For a current tag grouping or filter, a declared tag key and recorded usage that carries it. A deleted key remains available for time ranges whose usage still carries it. See Cost Attribution Tags.

For Admin API examples, export the control-plane URL and a read-scoped admin token:

# AISIX_CP includes /api and has no trailing slash.
# The local AISIX Enterprise quickstart uses http://localhost:8080/api.
export AISIX_CP="YOUR_AISIX_CONTROL_PLANE_API_URL"
export AISIX_TOKEN="YOUR_ADMIN_TOKEN"

With the prerequisites ready, start by narrowing the usage you want to analyze.

Filter and Group Usage​

The organization-level Cost Analysis page groups usage from every environment in the organization by one dimension at a time. The Filter section selects the usage to analyze, and Statistics breaks it down. For example, filter on business_group=cloud, then group by the department tag to see how that business group's spend splits across departments.

The Filter section comes first:

  • Time range: the last 24 hours, 7 days, or 30 days, or a custom range. The page reads whole hours, so the start is rounded down and the end up to the hour.
  • Filters: environments, team, member, model, and any number of tag key=value filters. Filters on different tag keys must all match, and several values of one key match any of them. A drill-down from an API key row adds that key as a filter. Each active filter shows as a chip below the controls, and Clear filters removes them all.

The Statistics section then breaks the filtered usage down:

  • Group by: a tag key, team, member, API key, model, environment, day, or target model. See Compare a Model's Targets. Grouping by day shows a daily bar chart; other groupings show horizontal bars.
  • Sort by: cost, total tokens, input tokens, output tokens, requests, or successful requests. Groups are listed largest first.
  • Refresh: reloads the figures, together with the tag keys and the names of teams, members, and environments.

Select a row to drill down. The page applies that group as a filter and chooses the next grouping not already fixed by the filters. Use the breadcrumb to return to an earlier step.

When grouping by team or by a tag key, a Budget (current period) column shows the matching team or tag budget's current-period spend and limit. When grouping by API key, each key row links to the request logs of the environment the key belongs to, filtered to that key.

After a control-plane upgrade, the page shows the same backfill notice as the Usage view. The figures remain incomplete until the backfill finishes. Cost is in the organization's currency.

To see how a row's cost was calculated, open its cost breakdown.

Interpret Special Groups and Labels​

Cost Analysis preserves historical usage even when its current resource or tag metadata has changed. The following labels explain how that history appears:

Label or rowMeaningWhat you can do
Not setThe usage has no value for the selected grouping.Review the row as a whole; it cannot be drilled into.
Day rowUsage from one UTC day.Change the time range or grouping; day rows cannot be drilled into.
Other N groupsThe sum of groups beyond the first 50. The listed groups and this row add up to the total.Narrow the filters to bring a group into the first 50.
DeletedA tag key or resource no longer exists, but selected usage still refers to it. Resources show the first eight characters of their ID.Select the badge to view and copy the full resource ID. For a deleted tag key, type a value or drill down from a group because value suggestions are unavailable.
RemovedA member no longer belongs to the organization.Select the badge to view and copy the member ID.
Name unavailableThe dashboard could not load enough metadata to confirm the resource's current state.Retry the view; this label does not mean the resource was deleted.

Tag controls offer every declared key and any deleted key present in the selected usage. Value suggestions come from current caller API keys, teams, members, and recent usage. When the organization has no declared tag keys, the controls link to Tag Keys, and tag filtering remains unavailable until a key is declared.

Compare a Model's Targets​

A model row groups usage by the name the caller addressed. To see how a model group's traffic was distributed, select Show targets on its row, or select Target model in Group by.

Each target row shows its Provider key, the Billed amount, and the Platform cost. Platform cost prices the usage at the target's own price, without the group's price or off-peak discount. Usage with no target price appears beside the amount as a number of unpriced attempts.

The counts are attempts on each target, including failed attempts and fallbacks. They can therefore add up to more than the model's request count.

Usage of a model called directly stays on that model's row, including responses served from the gateway's cache. Usage of a model group that reached no target falls in Not set. This includes a group response served from the cache and a request that failed before target selection.

Target rows and platform cost cover only usage recorded after the control-plane release that added them, so an earlier range can show less than the model row. Show targets and the grouping are unavailable while filtering by team, member, API key, or tag.

For example, suppose the chat model group distributes requests across several targets. The following query compares what each target served, what it billed, and what it cost at its own price:

GET /usage_analysis?group_by=target_model&model=chat

The endpoint takes the same permission as other usage reads. A target comparison cannot be combined with team, member, caller API key, or tag filters; such a request returns 400. Each group carries:

FieldMeaning
keyThe target model's ID. It is null for usage of a model group that reached none of its targets, such as a cache hit.
nameThe target model's current name, or null once it no longer exists.
environment_idThe environment the target model belongs to. It is absent once the model no longer exists.
provider, provider_key_nameThe vendor ID and display name of the target's provider key, or null once the model or the key no longer exists.
costThe billed amount, in the organization's currency. The response also returns it as cost_usd, a deprecated name.
prompt_tokens, completion_tokens, total_tokensToken counts of the attempts.
request_countAttempts on this target, counting each failed attempt and fallback.
success_countAttempts on this target that succeeded.
upstream_costThe summed platform cost, or null when every attempt was unpriced.
unpriced_request_countAttempts on this target whose upstream_cost is null. An attempt that reached no target is not counted.

remainder and total also carry upstream_cost and unpriced_request_count.

Read the Cost Breakdown​

A recorded cost is not always a token count multiplied by one listed rate. An input-token tier can change the rates for a long request. A service-tier entry can replace them, and a model group's off-peak discount can reduce the result.

The cost breakdown shows which pricing conditions applied, what was billed under each one, and the resulting average unit prices.

Open a Breakdown​

Every row of the Cost Analysis table, including Not set and Total, has an expander before its label. Select Show cost breakdown to open it under the row with the page's current time range and filters. Select Hide cost breakdown to close it.

The Other N groups row has no breakdown. Narrow the filters until the group you need is listed. Reloading the figures closes every open breakdown.

The breakdown lists one block per pricing condition, largest cost first. A condition is one combination of these columns:

ColumnMeaning
ModelThe model name the caller addressed. For a model group, this is the group, not the model that served the request.
Target modelThe model the attempt was dispatched to: the model itself when it was called directly, or one of the group's targets. It is — when no model was dispatched, such as a response a model group's request got from the gateway's response cache. A cache hit on a model called directly names that model.
Price tierThe input-token tier whose rates billed any of the request's billed items, such as Tier 2 (no upper limit). It is — when no billed item used a tier's rates, such as under a flat price. A request on the decision endpoints or billed by audio duration is listed under its tier when any of its items billed at the tier's rates, although its Price tier in Request Logs is empty.
Service tierThe tier the upstream reported serving the request at, or Default when it reported none.
PeriodOff-peak when the request completed inside its model group's off-peak discount, otherwise Normal.
PriceModel group price when the model group's price billed the usage. Target model price when the dispatched model's own price did: a model called directly, a model group without a price, and the requests a group price does not cover, which are those on the decision endpoints and those the target bills by audio duration.
Usage sourceReported by upstream when the upstream reported the usage. Estimated by gateway when the upstream left at least one of the request's token counts missing or zero and the gateway estimated it. A pricing condition with both kinds of usage is listed twice, once for each source.

Within a condition, each Billed item has its own line with the Quantity billed, the Average unit price, and the Amount. A condition the off-peak discount applied to ends with an Off-peak discount line that shows what it took off. A condition that cost nothing, such as cache hits or usage of a model without a price, shows a single No charge line.

Billed itemcomponent in the Admin APIQuantityRate
InputpromptInput tokens not read from a cachePrompt
Cache readcache_readInput tokens read from a prompt cacheCache read, or Prompt when blank
Cache write (5 min)cache_writePrompt-cache writes with a five-minute lifetime, and writes the upstream does not split by lifetimeCache write, or Prompt when blank
Cache write (1 hour)cache_write_1hPrompt-cache writes with a one-hour lifetimeThe one-hour write rate
OutputcompletionOutput tokens other than reasoningCompletion
ReasoningreasoningReasoning tokensReasoning, or Completion when blank
Decisions inputdecisions_promptInput tokens not read from a cache, on the decision endpointsPrompt under Decisions pricing
Decisions cache readdecisions_cache_readInput tokens read from a prompt cache, on the decision endpointsCache read under Decisions pricing
Decisions cache writedecisions_cache_writePrompt-cache writes, on the decision endpointsCache write under Decisions pricing
Decisions outputdecisions_completionOutput tokens other than reasoning, on the decision endpointsCompletion under Decisions pricing
Decisions reasoningdecisions_reasoningReasoning tokens, on the decision endpointsReasoning under Decisions pricing
Audio durationdurationSeconds of audioAudio per minute

The rates come from the price that billed the request, after applying its input-token tier and service-tier entry. Each item keeps the name of its original rate when a blank rate falls back. For example, cache reads billed at the Prompt rate remain Cache read.

A request on the decision endpoints uses a Decisions item for each part that the price's decision-endpoint rates billed:

  • A part whose decision-endpoint rate is set is billed under that rate's item.
  • A part whose decision-endpoint rate is blank falls back the same way the rates do, and is billed under the item of the rate it reaches. For example, with only a decision-endpoint Prompt rate set and the base Cache read rate blank, cache reads bill at the decision-endpoint Prompt rate under Decisions cache read. With the base Cache read rate set, they bill at it under Cache read.
  • A one-hour cache write has no decision-endpoint rate of its own. It is billed under Decisions cache write when that rate is set, otherwise under Cache write (1 hour) when the price sets a one-hour rate, and otherwise under the same item as the request's other cache writes.
  • When the price sets no decision-endpoint rate at all, decision-endpoint requests are billed under the base items, like any other request.

A summary above the table gives each billed item's share of the amounts, such as Input 62.5% · Output 37.5%, followed by the off-peak savings.

Export CSV writes one line per condition and billed item, including the average unit price. Additional lines report the off-peak discount as a negative amount, usage without a breakdown, and the total. The usage_estimated column identifies the usage source and is true for usage the gateway estimated.

When the breakdown holds estimated usage, the Total line states its cost in the organization's currency. For example, a USD organization can show incl. $1.20 on usage estimated by the gateway (3 requests). The request count is left out when it is zero, which happens when only retry or fallback attempts were estimated.

How the Numbers Add Up​

Start with the billed items in one condition. For example, if input costs 18 and output costs 4.8 before an off-peak discount of 11.4, the condition costs 18 + 4.8 - 11.4 = 11.4. Adding every condition, plus any No breakdown amount, produces the row's Total.

The remaining rules explain why a condition or average price can differ from a simple token count multiplied by one base rate:

SituationHow it appears in the breakdown
The price changed during the selected rangeAverage unit price is the amount divided by the quantity. It is weighted by quantity across the old and new rates. Without a price change, it equals the configured rate. Token prices are shown per 1M tokens; audio-duration prices are shown per minute.
A request crosses an input-token thresholdAISIX selects one tier from the request's input tokens, including cache reads and writes. Every billed item uses that tier; a request is never split across tiers.
The upstream reports a service tierA matching service-tier entry replaces the base rates and input-token tiers. If no entry matches, the request uses the base rates, but Service tier still shows what the upstream reported.
The gateway fills in a missing token countThe whole request is marked Estimated by gateway and its filled counts use the normal rates. Estimation changes the usage source, not the price.
A request retries or fails overThe request count belongs to the first attempt's usage source. Later attempts add their quantities and cost to their own conditions without adding another request, so a fallback with estimated usage can contribute estimated cost with no request count.
A model-group request completes off-peakThe group's multiplier applies to the request's whole cost. Off-peak discount is the sum of the billed amounts multiplied by one minus that multiplier.

Usage That Has No Breakdown​

The control plane records the breakdown when it prices a request and does not recalculate earlier usage. Usage recorded before the control-plane release that added the breakdown has none. The same applies to usage recorded by an older control-plane instance during a rolling upgrade.

When the selected range contains such usage, a notice shows when breakdown data begins and the amount without one. A No breakdown line carries that amount so the breakdown's Total still matches the row.

Cost Breakdown of a Single Request​

Expanding a row in Request Logs shows Cost breakdown with one line per billed item. The UI uses the organization's currency symbol; for example, a USD organization can show Input: 1,320 tokens × $0.15 / 1M tokens = $0.000198. An Off-peak discount line appears when the discount applied.

The section is absent from rows recorded before breakdown data existed and from rows with nothing to bill, such as a cache hit or a request to an unpriced model.

The usage event carries the same figures as cost_components, a list of component, quantity, and amount, and off_peak_discount, so the sum of the amounts minus off_peak_discount is cost. Both are null on events recorded before the breakdown existed, and exports carry them.

Automate Cost Analysis​

The Admin API exposes the same usage groups and cost breakdowns as the dashboard. Use it to export every group, reconcile costs in another system, or compare pricing conditions across a business dimension.

GET /usage_analysis returns the dashboard's figures, and GET /usage_analysis/tag_keys returns the tag keys present in a time range, including deleted keys. Both are documented in the AISIX Enterprise / Cloud Admin API. To retrieve more groups than the dashboard shows, see Retrieve Every Group Through the Admin API.

Read the Breakdown Through the Admin API​

GET /usage_analysis/breakdown returns the breakdown that Cost Analysis shows. It takes the same from and to and the same filters as GET /usage_analysis, and the same permission as other usage reads. Two more parameters narrow it to one group of any grouping:

  • target_model_id limits it to usage dispatched to one model. Like group_by=target_model, it and unset=target_model cannot be combined with team_id, member_id, api_key_id, or tag, and such a request returns 400.
  • unset limits it to usage with no value for a dimension, named as in group_by: tag:<key>, team, member, api_key, model, or target_model. Repeat it for several dimensions.

To compare a breakdown across members, tags, or another supported dimension, pass group_by. See Compare Costs Across Members, Tags, and Other Dimensions.

For a day group, pass the start and end of that UTC day as from and to. The following request breaks down a week of the chat model group:

curl -sS --get "${AISIX_CP}/usage_analysis/breakdown" \
--data-urlencode "from=2026-11-01T00:00:00Z" \
--data-urlencode "to=2026-11-08T00:00:00Z" \
--data-urlencode "model=chat" \
-H "Authorization: Bearer ${AISIX_TOKEN}"

The response has the following shape:

{
"group_by": null,
"currency": "USD",
"groups": [
{
"model": "chat",
"target_model": "31f302a2-d055-4f9f-a662-1dc4b84de9f7",
"target_model_name": "gpt-4o",
"price_tier": {
"index": 2,
"max_input_tokens": null
},
"service_tier": "priority",
"off_peak": true,
"group_price": true,
"usage_estimated": false,
"request_count": 120,
"components": [
{ "component": "prompt", "quantity": 3600000, "amount": 18 },
{ "component": "completion", "quantity": 240000, "amount": 4.8 }
],
"off_peak_discount": 11.4,
"cost": 11.4
}
],
"remainder": {
"group_count": 0,
"request_count": 0,
"components": [],
"off_peak_discount": 0,
"cost": 0
},
"total": {
"request_count": 120,
"components": [
{ "component": "prompt", "quantity": 3600000, "amount": 18 },
{ "component": "completion", "quantity": 240000, "amount": 4.8 }
],
"off_peak_discount": 11.4,
"cost": 11.4,
"group_count": 1,
"estimated_request_count": 0,
"estimated_cost": 0
},
"covered_from": "2026-11-01T08:00:00Z",
"uncovered_cost": 3.2
}

❶ group_price maps to the dashboard's Price column. true means the model group's price billed the usage; false means the target model's own price did.

❷ usage_estimated maps to Usage source. true means the gateway estimated at least one token count; false means the upstream reported the usage.

❸ remainder sums every pricing condition omitted from this page. The listed groups plus the remainder add up to the total.

❹ total covers every page. Its estimated request count and cost sum only conditions whose usage was estimated. A request is counted once, under its first attempt, while the components and cost include every attempt.

❺ covered_from marks the first hour with breakdown data. Add uncovered_cost to total.cost to reconcile the result with GET /usage_analysis for the same range and filters.

The response orders groups by cost, largest first. It returns at most limit groups beginning at offset; see Retrieve Every Group Through the Admin API. The currency field identifies the organization's currency.

For the complete response schema, see the AISIX Enterprise / Cloud Admin API.

Compare Costs Across Members, Tags, and Other Dimensions​

Use group_by when you want to compare the pricing conditions across a business dimension rather than inspect one filtered group. For example, the following request compares a week of costs across members:

curl -sS --get "${AISIX_CP}/usage_analysis/breakdown" \
--data-urlencode "from=2026-11-01T00:00:00Z" \
--data-urlencode "to=2026-11-08T00:00:00Z" \
--data-urlencode "group_by=member" \
-H "Authorization: Bearer ${AISIX_TOKEN}"

Each returned group still represents one pricing condition, but now identifies the member whose request produced it. The focused excerpt below omits unrelated response and pricing-condition fields:

Focused response excerpt
{
"group_by": "member",
"groups": [
{
"key": "e754c9ea-83c5-49e3-9e38-5d4fc782bd0e",
"name": "Alice",
"model": "chat",
"target_model_name": "gpt-4o",
"price_tier": {
"index": 2,
"max_input_tokens": null
},
"request_count": 18,
"components": [
{ "component": "prompt", "quantity": 420000, "amount": 2.1 },
{ "component": "completion", "quantity": 30000, "amount": 0.6 }
],
"cost": 2.7
}
]
}

❶ The response echoes the selected grouping. Without group_by, this field is null.

❷ key is the member ID, and name is the current display name. Usage without a member has a null key. If the member no longer exists, the name is also null.

The remaining fields identify the pricing condition and show what it cost. One member can therefore appear in several groups when their requests used different models, targets, price tiers, or other pricing conditions.

The same request can compare other dimensions:

Comparisongroup_by valueGroup identity
One tag keytag:<key>Tag value
TeamsteamTeam ID and current name
Caller API keysapi_keyKey ID, current name, and its environment_id while the key exists
EnvironmentsenvironmentEnvironment ID and current name
UTC daysdayDay

Any grouping can return a null key for usage that had no value for that dimension. A team, member, caller API key, or environment group carries its current display name, or a null name after the resource no longer exists. Tag and day groups do not carry names.

Every pricing condition already identifies its model and target model, so requests with group_by=model or group_by=target_model return 400.

For the complete response schema, see the AISIX Enterprise / Cloud Admin API.

Retrieve Every Group Through the Admin API​

The dashboard does not page. Its main table shows the 50 highest-ranked groups and summarizes the rest as Other N groups. An expanded cost breakdown shows up to 500 pricing conditions and summarizes the rest as Other conditions.

To analyze or export every group, page through GET /usage_analysis or GET /usage_analysis/breakdown sequentially:

offset=0
while :; do
page=$(curl -sS --get "${AISIX_CP}/usage_analysis/breakdown" \
--data-urlencode "from=2026-11-01T00:00:00Z" \
--data-urlencode "to=2026-11-08T00:00:00Z" \
--data-urlencode "group_by=member" \
--data-urlencode "limit=500" \
--data-urlencode "offset=${offset}" \
-H "Authorization: Bearer ${AISIX_TOKEN}")
echo "${page}" | jq -c '.groups[] | {member: .key, name, model, target_model_name, price_tier, usage_estimated, request_count, components, cost}'
offset=$((offset + 500))
[ "${offset}" -lt "$(echo "${page}" | jq '.total.group_count')" ] || break
done

This example reads 500 member groups at a time and prints one line per group. The paging fields work as follows:

  • limit sets how many groups a response lists, 50 by default and 500 at most. offset skips that many groups in sort order before the first one listed, and defaults to 0.
  • total.group_count is how many groups there are across every page.
  • remainder sums every group that is not on the page, before it as well as after it, so the listed groups plus remainder add up to total on every page.
  • Groups that tie in the sort order are ordered by key. Breakdown conditions then use the rest of their columns, with the usage source last and reported usage before estimated usage. Successive pages neither repeat nor skip a group as long as usage does not change between requests.

Choose a completed time range before paging. Usage from the current hour can change the ordering while the loop runs and cause a group to move between pages.

Read pages one after another, not in parallel. Every page recomputes the aggregation over the whole range, and parallel requests multiply its database memory use. The control plane does not cap concurrent aggregation queries.

Each aggregation query has 30 seconds. A timeout returns 503 with the error code QUERY_TIMEOUT and no groups. Narrow the time range or filters, then try again.

Next Steps​

Configure Cost Attribution Tags for organization-defined allocation dimensions, or review Model Pricing when a cost is missing or differs from the provider's pricing basis. Use Usage Reporting for organization-wide totals and reporting behavior.