Skip to main content
Version: 1.9.0

Cost Attribution Tags

Cost attribution tags let an organization attribute usage and cost to its own dimensions, such as departments, business groups, subsidiaries, projects, or cost centers. Declare the tag keys, then set key=value pairs on caller API keys, teams, and members, or read the values from a caller's JWT.

Every request is attributed to the tag values it resolves when it runs. Cost Analysis breaks down spend by those values, tag budgets cap it, and rate limit policies can match and split traffic on it.

Tags work the same way in AISIX Enterprise and AISIX Cloud.

Prerequisites​

Before starting, prepare the following:

  • The owner or admin role in the organization, or a custom role with tag_keys write permission, to declare tag keys. Setting tags on a resource needs write permission on that resource.
  • A gateway that records tags on its usage in every environment that should be attributed. A gateway at 1.6.x or earlier does not; see Gateway Version Requirements.
  • For the Admin API examples, the control-plane URL and an admin token with write scope:
# 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"
export TEAM_ID="YOUR_TEAM_ID"
export ENV_ID="YOUR_ENVIRONMENT_ID"

With the connection values ready, declare the tag keys the organization will use.

Declare Tag Keys​

A tag key must be declared in the organization before any resource can carry it. Declaring keys up front keeps the vocabulary consistent, so department is never also spelled dept or Department.

  1. Open Tag Keys in the organization navigation, then select New tag key.
  2. Enter the Key, such as department.
  3. Optionally enter a Display name and a Description.
  4. Select Declare key.

To declare a key through the Admin API:

curl -sS -X POST "${AISIX_CP}/tag_keys" \
-H "Authorization: Bearer ${AISIX_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"key": "department",
"display_name": "Department",
"description": "Cost center the usage is charged to."
}'

Tag keys follow these rules:

  • A key starts with a lowercase letter and contains only lowercase letters, digits, and underscores, up to 63 characters: ^[a-z][a-z0-9_]{0,62}$.
  • A key cannot be changed after it is declared. The display name and description can be edited at any time.
  • An organization can declare at most 32 keys. A request to declare one more returns 400 with the code TAG_KEY_LIMIT, and declaring a key that already exists returns 409.
  • Every member can read the declared keys. Writing them takes the tag_keys write permission.

Delete a Tag Key​

Deleting a tag key is refused with 409 and the code TAG_KEY_IN_USE while a budget, a rate limit policy, or a trust provider's tag_claims uses it. The error message names them; remove them first.

Otherwise, deletion removes the key from every caller API key, team, and member that carries it. Later requests are no longer attributed to it.

Recorded usage keeps its original tags. Cost Analysis continues offering the key, marked Deleted, for any selected range whose usage carries it.

Set Tags on API Keys, Teams, and Members​

Tags can be set on three resources. Each one carries its own tags, as an object of tag key to tag value:

ResourceDashboardAdmin API
Caller API keyThe Cost-attribution tags section of the key's create and edit forms, under the environment's API keysPOST /environments/{environment_id}/api_keys and PATCH /environments/{environment_id}/api_keys/{api_key_id}
TeamThe team's create and edit forms, under TeamsPOST /teams and PATCH /teams/{team_id}
MemberThe Tags panel on a member row in Members, and the Create user form in AISIX EnterprisePOST /members and PATCH /members/{user_id}

The dashboard offers the declared keys and suggests values the organization already uses for the selected key. GET /tag_keys/{key}/values returns the same suggestions, up to 500 distinct values.

Configured values from caller API keys, teams, and members appear first. Values found in the last 30 days of usage fill the remaining space, which is how values read from JWT claims appear. Reading values from usage requires the usage read permission.

In a very large organization, the usage lookup can time out. The response then contains only configured values, and you can still enter another value manually.

Tag values follow these rules:

  • A value is free-form text of 1 to 128 characters.
  • A value cannot contain control characters, such as a line break, and cannot start or end with whitespace, including non-breaking and full-width spaces. The control plane rejects such a value with 400.
  • Every key in a resource's tags must be declared in the organization.

In the Admin API, PATCH replaces the whole tags object. Send the complete set you want to keep. Send null or {} to clear every tag, and omit tags to leave them unchanged. For example, the following request sets two tags on a team:

curl -sS -X PATCH "${AISIX_CP}/teams/${TEAM_ID}" \
-H "Authorization: Bearer ${AISIX_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"tags": {
"department": "rnd",
"subsidiary": "emea"
}
}'

A member's tags belong to their membership in this organization, not to their account. An admin can change the tags, while changing the member's role still requires an owner.

Members who join through an invitation, single sign-on, or SCIM directory sync start without tags. Tags remain editable for directory-managed members because directory sync neither reads nor writes them.

To take values from the identity provider at request time, see Read Tags From JWT Claims.

How a Request Resolves Its Tags​

When a request authenticates, the gateway resolves its tags from the caller API key that authenticated it and, for a JWT caller, from the verified token. For each tag key, the nearest value wins:

  1. For a JWT caller, the value the trust provider's tag_claims reads from the token. See Read Tags From JWT Claims.
  2. Otherwise, the caller API key's own value.
  3. Otherwise, the value set on the team that the key is bound to.
  4. Otherwise, the value set on the member that owns the key.

Only the key's own team and member bindings participate. Other teams that the member belongs to contribute no tags.

A request carries one value per tag key, and values from different keys combine. One request can therefore take department from its key, business_group from its team, and subsidiary from its member. A request cannot supply its own tags; the gateway never reads them from request headers or the body.

For example, suppose the organization declares department, business_group, and subsidiary, and sets the following:

ResourceTags
Team platformdepartment=infra, business_group=cloud
Member Alicedepartment=rnd, subsidiary=emea
Key K1, bound to platform and owned by Alicedepartment=ml
Key K2, bound to platform and owned by AliceNone
Key K3, owned by Alice, with no teamNone

Requests resolve these tags:

Keydepartmentbusiness_groupsubsidiary
K1ml, from the keycloud, from the teamemea, from the member
K2infra, from the teamcloud, from the teamemea, from the member
K3rnd, from the memberNot setemea, from the member

Read Tags From JWT Claims​

For callers that authenticate with a JWT, a trust provider can supply tags from the verified token. Its tag_claims field maps tag keys to claim paths, which the gateway reads on every request.

Usage analysis, exports, tag budgets, and tag rate limits all use the result. You do not need to tag each caller API key or keep those tags synchronized with the identity provider.

The value applies to the request only. It is never written back to the caller API key, team, or member, so their pages do not show it.

The claim must be in the JWT that reaches the gateway. A field that only the identity provider's userinfo endpoint returns cannot be read, so configure the identity provider to add it to the token. For MCP clients that use interactive sign-in, the claim must be in the ID token.

Configure Tag Claims​

First declare each tag key. Then, in the environment's OIDC providers view, create or edit a provider. Under Tags from token claims (optional), select Add tag, select a declared key, and enter the claim path. The provider's row in the list shows its current mappings.

To set the same mappings through the Admin API, use tag_claims when you create or update the provider:

export OIDC_PROVIDER_ID="YOUR_OIDC_PROVIDER_ID"

curl -sS -X PATCH "${AISIX_CP}/environments/${ENV_ID}/oidc_providers/${OIDC_PROVIDER_ID}" \
-H "Authorization: Bearer ${AISIX_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"tag_claims": {
"department": "department",
"subsidiary": "org.subsidiary"
}
}'

PATCH replaces the whole tag_claims object. Send null or {} to remove every mapping, and omit the field to leave them unchanged.

The control plane returns 400 INVALID_REQUEST when a key is undeclared or does not match the tag-key format. It also rejects more than 32 mappings or an empty claim path. A mapped key cannot be deleted until its mapping is removed.

How a Claim Becomes a Tag Value​

The gateway reads a claim path as one top-level claim name first, so https://example.com/department works. When the token has no claim of that name, dots traverse nested objects, so org.subsidiary reads subsidiary inside org.

It then converts the claim's value:

Claim valueTag value
"rnd"rnd, unchanged
10421042, the number as text
["rnd", "ml", "rnd"]ml,rnd, the distinct elements sorted as text and joined with commas
["rnd"]rnd, the element itself
true, null, {}, or []Missing
["rnd", true], an array holding anything but strings and numbersMissing

The result must follow the same rule as any other tag value: 1 to 128 characters, no control characters, and no whitespace at the start or end. For an array, each element and the joined result must both satisfy this rule. A value that breaks it counts as missing and is never truncated or trimmed.

An array produces one tag value, not several. A user whose groups claim is ["ml", "rnd"] carries ml,rnd, so a budget on groups=rnd does not count that user's requests. Commas inside an element are not escaped, so ["a,b"] and ["a", "b"] produce the same value.

Fall Back and Set Defaults​

A tag claim is resolved per tag key and takes precedence over the caller API key, team, and member. When the claim is missing, the request falls back to the next of them for that key alone.

To keep requests without the claim visible, set a default on the caller API key, such as department=unassigned. Those requests then appear in Cost Analysis under that value, and you can set a budget on it. A request with no value for a tag key from any source is not subject to a budget or rate limit policy on that key.

When Values Change​

A value changed at the identity provider takes effect when a new token is issued. Usage already recorded stays with the value it carried when it ran, so a move between departments does not re-attribute history.

A user signed in through interactive MCP sign-in keeps the claims recorded at sign-in, including across session refreshes. The user receives a new claim value only after signing in again. A change to the provider's tag_claims applies to later requests in either case.

Choose Low-Cardinality Claims​

Prefer claims with few distinct values, such as a department or cost center. Each distinct value is a separate group in Cost Analysis and a separate counter in a rate limit policy that groups by the tag.

Avoid mapping a per-person value, such as an email address, when many people share one caller API key. Usage is summarized per key and tag set, so this greatly increases the volume of summary data. The value also enters usage records and exports, so consider the privacy impact.

Behavior on Shared Keys​

Tags read from claims can differ between requests on one caller API key, which has two visible effects:

  • Tag budgets can reject one token and allow another on the same key. The aisix_budget_* gauges are labeled by key, so they reflect the verdict of the most recent request.
  • The usage of a completed batch carries the tags of the caller whose request observed the completion, not the caller who submitted the batch.

Troubleshoot Tag Claims​

To see what a claim produced, group by the tag key in Cost Analysis, or expand the request's row in Request Logs to read its Tags. A request that shows a value from its key, team, or member, or that falls in Not set, did not get a value from its claim.

The reason appears only in the gateway's debug log. Allow debug lines for the aisix::auth target through log_level or RUST_LOG, for example info,aisix::auth=debug, and restart the gateway. Each dropped claim then writes one claim yields no tag value line per request, with provider, tag_key, and reason. The line never includes the value.

reasonMeaning
claim_absentThe token has no claim at that path.
value_typeThe value is a boolean, null, or an object.
empty_arrayThe value is an empty array.
array_element_typeAn array element is not a string or a number.
lengthA value, an array element, or the joined result is not 1 to 128 characters.
control_characterA value, an array element, or the joined result contains a control character.
surrounding_whitespaceA value, an array element, or the joined result starts or ends with whitespace.

Attribution Is Fixed When the Request Runs​

The gateway records the resolved tags and the key's team on every usage event of the request, including retries, failovers, and the gateway's own rejections. The control plane stores them with the event and attributes cost by what was recorded.

Tag changes apply to later requests only. Changing resource tags, claim mappings or values, or a key's team binding does not re-attribute recorded usage. Neither does deleting a tag key.

Usage recorded before a tag was set has no value for it and appears as Not set. The same applies to usage from a gateway that does not record tags.

Budget by Tag​

A tag budget caps the spend attributed to one tag value across every environment in the organization. In Budgets, select the Tag scope, choose the key, and enter its value.

In the Admin API, set scope to tag and scope_ref to <key>=<value>, such as department=rnd. The key must be declared, and the value is everything after the first =. limit_cents uses the minor unit of the organization's currency.

curl -sS -X POST "${AISIX_CP}/budgets" \
-H "Authorization: Bearer ${AISIX_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"display_name": "R&D monthly",
"scope": "tag",
"scope_ref": "department=rnd",
"limit_cents": 50000,
"period": "month",
"hard_stop": true
}'

A tag budget follows the same period, hard-stop, and alert rules as other budgets. A tag value can have one budget, and spend is counted from the tags recorded on each request's usage.

After a hard-stop tag budget reaches its limit, later requests with the same tag value receive HTTP 429 with code budget_exceeded. The response sets scope to tag and scope_ref to the key=value pair.

Creating a tag budget returns 422 with code DP_INCOMPATIBLE while any environment in the organization has a registered gateway that does not record tags. This applies to gateways at 1.6.x or earlier, whose usage would not count toward the budget.

Rate-Limit by Tag​

A conditional rate limit policy accepts tag:<key> as a condition dimension and in group_by. The key must be declared. For example, the following policy gives each department its own limit of 600 requests per minute:

curl -sS -X POST "${AISIX_CP}/environments/${ENV_ID}/rate_limits" \
-H "Authorization: Bearer ${AISIX_TOKEN}" \
-H "Content-Type: application/json" \
-d '{
"name": "per-department-rpm",
"group_by": ["tag:department"],
"limits": { "rpm": 600 }
}'

Two caller API keys that resolve department=rnd share one counter, and a key that resolves department=sales has its own. A request without a value for the tag does not match a condition on it, even a negated one, and is not subject to a policy that groups by it.

Saving a policy that uses tag: is refused with 422 and the code DP_INCOMPATIBLE while any registered gateway in that environment runs 1.6.x or earlier, because those releases cannot load it.

Read Tags in Usage Events and Request Logs​

Every usage event carries tags, the request's resolved tags, and team_id, the team bound to the caller API key. Both fields are omitted when empty.

Observability exporters deliver them to external systems, while Request Logs shows them in row details and exports. See Attribute Events to a Team and Tags for each exporter's field names. Tags never become Prometheus metric labels.

Gateway Version Requirements​

Tags are resolved and recorded by the gateway. A gateway at 1.6.x or earlier ignores them:

  • It loads tagged caller API keys, teams, and members, but ignores the tags field. The dashboard shows a partial-compatibility notice for those gateways.
  • Its usage carries no tags and no team_id, so it appears as Not set in Cost Analysis.
  • It drops a rate limit policy that uses tag:.

Reading tags from JWT claims needs a gateway at 1.9.0 or later. An older gateway loads the trust provider but ignores tag_claims, so it attributes requests by key, team, and member only.

Saving a provider with tag_claims still succeeds while such a gateway is registered. The dashboard shows a warning, and the Admin API response carries a field_ignored warning for tag_claims. Upgrade the control plane first, then the gateways.

The control plane refuses to save tag budgets and tag rate limit policies while such a gateway is registered, as described above. That check can only see the gateways registered when the budget or policy is saved.

Known gap

A gateway rolled back to 1.6.x does not enforce existing tag rate limits, and its usage does not count toward tag budgets. The same applies when a 1.6.x gateway joins after a tag rate limit policy or budget was saved. The affected environment's Overview page shows the rejected configuration. Upgrade the gateway to restore enforcement and counting.

Next Steps​

Use Cost Analysis to group and inspect attributed usage. Then use Budgets and Budget Alerts and Notifications to cap and monitor tag spend.