Tracking inference spend
Inference spend is the cost of the model traffic in a period, together with the tokens that traffic consumed. An exchange is one request to a model and the response that the model returns. The gateway router records each exchange, and Akka computes spend from the recorded exchanges. The portfolio service prices each exchange when it stores the exchange, at the rate that the pricing catalog holds for the instant the exchange was served. If the served name has no price at that time, the service prices the exchange later, once a published price covers the instant it was served.
Two command groups work together. akka optimize pricing-catalog publishes the prices, and akka optimize spend reads the figures computed from them. Each price belongs to a served name: the model name that the provider reports in the model field of its response, for example claude-sonnet-5-5. Publish a price for each served name before your services send traffic to that model. An exchange that has no price counts in the token totals and adds nothing to the spend. When traffic is recorded before its served name has a price, the service prices it after you publish a price that takes effect at or before that traffic. See Find unpriced traffic.
Before you begin
The commands on this page call the portfolio service of your Akka Optimize deployment. Before you run them:
-
Log in with the Akka CLI. The commands send the access token of your current context.
-
Set the
PORTFOLIO_URLenvironment variable to the address of the portfolio service. Your Akka team provides it. The--urlflag does not apply to these commands, and--regionis refused.
Reading prices and spend needs read access to the Akka Optimize portfolio. Publishing prices, including a dry run, needs write access.
Spend has figures only for traffic that the gateway router recorded. Traffic that was not recorded does not appear. See Routing requests to models for how requests reach the gateway router.
Publish prices
The pricing catalog holds the price of each served name as rates per million tokens. A response without a model field is unpriced.
The catalog is versioned. A published version is never edited, so an exchange that a version priced keeps that price. To change a price, publish a new version that takes effect later. See Change a price.
Write a catalog document
A catalog document is JSON:
{
"effectiveAt": "2026-09-15T16:00:00Z",
"source": "list prices",
"models": [
{
"model": { "provider": "example", "name": "frontier", "version": "1" },
"servedName": "frontier-model",
"assigned": false,
"currency": "USD",
"input": 5.00,
"output": 25.00
},
{
"model": { "provider": "example", "name": "finetune", "version": "1" },
"servedName": "finetune-model",
"assigned": true,
"currency": "USD",
"input": 0.10,
"output": 0.30,
"cacheRead": 0.01
}
]
}
| Field | Meaning |
|---|---|
|
Required. The ISO-8601 instant in UTC from which the prices apply. It must be later than the latest price of every served name that the document carries. It can be in the future or in the past. When the document holds the first price of a served name and |
|
Optional. Free text that records where the prices came from. |
|
Required. The |
|
Required. The served name that this price applies to. A served name can appear once in a document. |
|
Optional. |
|
Required. An upper-case ISO 4217 code, such as |
|
Required. The rate per million input and output tokens. |
|
Optional. The rate per million tokens of these classes. |
An exchange is priced only when every token class that it used has a rate. A rate that you leave out means the class has no price, not that it costs nothing. If a model does not charge for a class, set its rate to 0. If you leave out cacheRead for a model whose traffic uses cached input, that traffic is unpriced. An OpenAI model that charges for cache writes, GPT-5.6 and later, needs a cacheWrite rate, or its traffic that wrote to the cache is unpriced. The service ignores a field that the document does not define, so a misspelled rate counts as a rate that you left out.
A version can price some served names and leave out others. The served names that it leaves out keep their earlier price.
Start from the example catalog
Exchanges are unpriced until you publish a catalog version. The example in this section holds list prices for some common OpenAI and Anthropic models. Review it and adjust it before you publish it.
The rates are the list prices that OpenAI API pricing and Claude API pricing stated on October 18, 2026. List prices change, so check each rate on the provider’s page before you publish.
{
"effectiveAt": "2026-10-18T00:00:00Z",
"source": "OpenAI and Anthropic list prices, taken 2026-10-18",
"models": [
{
"model": { "provider": "openai", "name": "gpt-5.2", "version": "2025-12-11" },
"servedName": "gpt-5.2-2025-12-11",
"assigned": false,
"currency": "USD",
"input": 1.75,
"cacheRead": 0.175,
"output": 14.00,
"reasoning": 14.00
},
{
"model": { "provider": "openai", "name": "gpt-5.4-mini", "version": "2026-03-17" },
"servedName": "gpt-5.4-mini-2026-03-17",
"assigned": false,
"currency": "USD",
"input": 0.75,
"cacheRead": 0.075,
"output": 4.50,
"reasoning": 4.50
},
{
"model": { "provider": "openai", "name": "gpt-5.4-nano", "version": "2026-03-17" },
"servedName": "gpt-5.4-nano-2026-03-17",
"assigned": false,
"currency": "USD",
"input": 0.20,
"cacheRead": 0.02,
"output": 1.25,
"reasoning": 1.25
},
{
"model": { "provider": "anthropic", "name": "claude-fable", "version": "5.1" },
"servedName": "claude-fable-5-1",
"assigned": false,
"currency": "USD",
"input": 10.00,
"cacheRead": 0.25,
"cacheWrite": 12.50,
"output": 50.00
},
{
"model": { "provider": "anthropic", "name": "claude-opus", "version": "5.5" },
"servedName": "claude-opus-5-5",
"assigned": false,
"currency": "USD",
"input": 4.00,
"cacheRead": 0.20,
"cacheWrite": 5.00,
"output": 20.00
},
{
"model": { "provider": "anthropic", "name": "claude-sonnet", "version": "5.5" },
"servedName": "claude-sonnet-5-5",
"assigned": false,
"currency": "USD",
"input": 2.00,
"cacheRead": 0.10,
"cacheWrite": 2.50,
"output": 10.00
}
]
}
Change the example before you publish it:
-
Keep only the models that your traffic uses. Each
servedNamemust be the string that your provider’s responses report in theirmodelfield. OpenAI responses report the dated snapshot, such asgpt-5.4-mini-2026-03-17, also when the request namesgpt-5.4-mini. To see the served names that your traffic reports, divide a recent period bymodel, as in Read spend for a period. Add an entry for each served name in that division that has no price yet. -
Set
effectiveAtto the instant from which the prices apply, andsourceto where they came from. To price traffic that is already recorded, seteffectiveAtat or before its earliest exchange. Only the first price of a served name can take effect that early. See Find unpriced traffic. -
Where your organization has a negotiated rate, state that rate and set
assignedtotrue.
Check the document with --dry-run before you publish it.
Check and publish
Check a document without publishing it:
akka optimize pricing-catalog apply -f catalog.json --dry-run
The service validates the document and states the version it would publish. Nothing is written.
Publish it:
akka optimize pricing-catalog apply -f catalog.json
The command asks you to confirm. Pass --force to skip the question in a script. To read the document from standard input, pass -f -. Without a terminal, publishing requires --force, --disable-prompt, or AKKA_DISABLE_PROMPTS=true; otherwise, the command stops without publishing.
A document that the service refuses is not published, and the error names what is wrong, for example a rate that is missing or negative, or an effectiveAt that is not later than the latest price.
Read the catalog
Read the prices in force now:
akka optimize pricing-catalog get
Version 2
Effective at 2026-10-01T00:00:00Z
Published at 2026-09-30T09:12:40.118204Z
Source negotiated rate
SERVED NAME PRICED BY MODEL CURRENCY ASSIGNED INPUT CACHE-READ CACHE-WRITE OUTPUT REASONING
finetune-model 1 example/finetune/1 USD yes 0.10 0.01 — 0.30 —
frontier-model 2 example/frontier/1 USD yes 4.00 — — 20.00 —
get lists every served name that has a price in force, ordered by served name, each with the entry of the version that prices it. PRICED BY names that version. The top block names the version that most recently took effect. In this example, version 2 priced only frontier-model, so finetune-model keeps the price that version 1 set. A class with no price shows —. Pass --at with an ISO-8601 instant to read the prices in force at that time.
To see when each version took effect, list the versions:
akka optimize pricing-catalog versions list
The list is newest first.
Change a price
get -o json writes a catalog document that apply takes. When you publish it, the service ignores the fields that the service sets: version and publishedAt at the top, and version on each entry. The service refuses the document if you publish it unchanged, because its effectiveAt is not later than the prices it carries.
-
Write the prices in force to a file:
akka optimize pricing-catalog get -o json > catalog.json -
Keep only the entries that you change, and edit their rates. The service never removes a version, so an entry that repeats a price makes the catalog larger and changes nothing.
-
Run
akka optimize pricing-catalog versions list, and look for a version that takes effect after theeffectiveAtin the file.getshows the prices in force now, so the file holds no version dated in the future and no version published after you read it. A document dated after such a version replaces its rate for every served name that both price. -
Set
effectiveAtto an instant later than theeffectiveAtin the file. -
Check the document:
akka optimize pricing-catalog apply -f catalog.json --dry-run -
Publish it:
akka optimize pricing-catalog apply -f catalog.json
Read spend for a period
A period is a window of whole UTC hours. --from is inclusive and --to is exclusive. The service refuses a window that is not whole hours, or whose end is not after its start.
akka optimize spend get --from 2026-09-15T16:00:00Z --to 2026-09-15T17:00:00Z
The answer gives the number of exchanges, the tokens, the spend, and what is unpriced. It also states whether the figures are complete:
-
complete: every hour of the window has been reported on, and no exchange was lost. -
incomplete: some hours have not been reported on yet, for example because the window includes the current hour, or some exchanges were lost. -
unknown: no hour of the window has been reported on yet.
An hour is reported on about a day after it ends, with or without traffic, when the gateway router recorded for the whole hour. Until then, a window that holds it is not complete. An hour with a minute in which no gateway router instance was recording, for example during a restart, can stay unknown, and a window that holds it then never reads complete.
Money is reported with the digits that the service computed. A figure that has no value says why, and never reads as zero.
Divide the period
Pass --by to divide the period into rows. The rows are ordered by spend, biggest first, and always add up to the total.
akka optimize spend get --from 2026-09-15T16:00:00Z --to 2026-09-15T17:00:00Z --by model
MODEL EXCHANGES TOKENS SPEND UNPRICED UNPRICED TOKENS
frontier-model 69 1575+ 0.0255750000000000 USD 0 0
finetune-model 12 285+ 0.0000615000000000 USD 0 0
TOTAL 81 1860+ 0.0256365000000000 USD 0 0
A + after a token count marks a lower bound: some exchanges did not report every token class, so the true count can be higher. This is common, because providers do not report every class. For example, OpenAI responses report cache-write tokens only for the models that charge for them, GPT-5.6 and later, and Anthropic responses do not report reasoning tokens. The command prints a note that names the classes that were not reported.
--by |
One row for each |
|---|---|
|
No division. The answer is the period total, as when you leave out |
|
Served name. |
|
Use case that the gateway router assigned. A row shows the label of the use case, or its id when it has no label. See Routing requests to models. |
|
System id that the request named, and one row, |
|
Route that the gateway router took to the destination of the exchange. |
|
Value that the tagging ruleset |
A comma-separated list, such as |
Combination of the listed divisions, at most eight in one read. |
A served name longer than 256 bytes of UTF-8 is held and shown as sha256: followed by the hex SHA-256 of those bytes.
A route holds how the gateway router reached the destination of an exchange: the basis, the routing ruleset and its revision, the rule, and the destination. Each revision of a ruleset is a row of its own, so a period that spans a ruleset change shows the spend of each revision. See Routing requests to models.
akka optimize spend get --from 2026-09-12T00:00:00Z --to 2026-09-13T00:00:00Z --by useCase,route
USE-CASE ROUTE EXCHANGES TOKENS SPEND UNPRICED UNPRICED TOKENS
support triage rule optimize@7 summarise → frontier 2140 51200+ 0.6400000000000000 USD 0 0
— none 310 7400+ 0.0930000000000000 USD 0 0
— unknown 120 2900+ 0.0310000000000000 USD 0 0
billing reports manifest → finetune-billing 980 22100+ 0.0042300000000000 USD 0 0
support triage rule optimize@8 summarise → finetune 610 14800+ 0.0028400000000000 USD 0 0
TOTAL 4160 98400+ 0.7710700000000000 USD 0 0
A route reads as its basis, then ruleset@revision and the rule where the route has them, then → and the destination where it has one. The basis is one of these:
| Basis | How the gateway router reached the destination |
|---|---|
|
A rule chose the destination, with or without a classifier. |
|
The classifier of a rule failed, and the fallback of the rule chose the destination. |
|
No rule in the ruleset matched, and the default destination of the ruleset applied. |
|
No rule matched, and the use case manifest chose the destination. |
|
A rule refused the request. There is no destination. |
|
The ruleset could not run. |
|
No routing ruleset ran. |
|
The record of the exchange does not state the route. |
Divide spend by a tag
A tag is a value that a tagging ruleset gives an exchange, and KEY is the name of the tagging ruleset. See Tagging traffic.
akka optimize spend get --from 2026-09-15T16:00:00Z --to 2026-09-15T17:00:00Z --by model,tag:team
MODEL TEAM EXCHANGES TOKENS SPEND UNPRICED UNPRICED TOKENS
frontier-model payments 41 948+ 0.0154000000000000 USD 0 0
frontier-model search 19 441+ 0.0071850000000000 USD 0 0
frontier-model unknown 6 120+ 0.0019200000000000 USD 0 0
frontier-model — 3 66+ 0.0010700000000000 USD 0 0
finetune-model payments 12 285+ 0.0000615000000000 USD 0 0
TOTAL 81 1860+ 0.0256365000000000 USD 0 0
Traffic that no rule of the ruleset claims gets the default destination of the ruleset as its value, such as unknown above. The row with no name holds the traffic that the ruleset did not decide for: for example, traffic served before the ruleset was in force, traffic for which the ruleset failed, or a request that a ruleset ahead of it denied. The rows still add up to the total. The gateway router sets the tag when it serves the exchange, so a ruleset that you change later does not move a figure.
Attribute spend to systems
A system id names the AI system that a request was sent for. The request names it in the x-akka-system-id header. An agent of an Akka service sends this header with its identity headers. See Identity headers. Any other client sets the header itself. Traffic that names no system id is the row unidentified.
Divide a period by system id:
akka optimize spend get --from 2026-09-15T00:00:00Z --to 2026-09-16T00:00:00Z --by system
SYSTEM ID EXCHANGES TOKENS SPEND UNPRICED UNPRICED TOKENS
svc-support 4210 98400+ 1.2400000000000000 USD 0 0
svc-search 2100 47600+ 0.5900000000000000 USD 0 0
svc-billing 980 22100+ 0.2900000000000000 USD 0 0
svc-triage 1200 23400+ 0.2900000000000000 USD 0 0
unidentified 120 2900+ 0.0310000000000000 USD 0 0
TOTAL 8610 194400+ 2.4410000000000000 USD 0 0
The answer has a row for every system id with traffic in the period. No system id is left out or folded into another row, so the rows add up to the total. system combines with the other divisions, for example --by system,model for what each system spent on each model.
Pass --system to read the spend of one system id. It works on spend get, spend compare and spend series, with any division:
akka optimize spend get --from 2026-09-15T00:00:00Z --to 2026-09-16T00:00:00Z --by model --system svc-support
MODEL EXCHANGES TOKENS SPEND UNPRICED UNPRICED TOKENS
frontier-model 3890 91800+ 1.2387000000000000 USD 0 0
finetune-model 320 6600+ 0.0013000000000000 USD 0 0
TOTAL 4210 98400+ 1.2400000000000000 USD 0 0
--system cannot select the traffic that named no system id: --system unidentified reads a system id named unidentified. A system id longer than 256 bytes of UTF-8 is held and shown as sha256: followed by the hex SHA-256 of those bytes, and --system takes either form.
List every system id that the service has seen:
akka optimize spend system-ids
SYSTEM ID CALLS FIRST CALL HOUR LAST CALL HOUR
svc-billing 41200 2026-06-02T09:00:00Z 2026-09-15T23:00:00Z
svc-legacy 0 — —
svc-search 88100 2026-08-30T14:00:00Z 2026-09-16T00:00:00Z
svc-support 96400 2026-05-11T08:00:00Z 2026-09-16T00:00:00Z
svc-triage 30500 2026-07-01T10:00:00Z 2026-09-15T22:00:00Z
Each row is one system id that the service holds. CALLS is how many exchanges named it, and FIRST CALL HOUR and LAST CALL HOUR are the first and the last UTC hour in which one did. These are whole hours, not instants. A system id whose exchanges left the records before the service counted them in an hour has no calls and no hours, such as svc-legacy above. To list the last exchanges of a system id, run akka optimize spend recent --system ID. See List the most recent exchanges.
The service does not check a system id. A client sends the value that it chooses, unless your deployment overwrites the header at its own edge. The service keeps every system id that it sees, and nothing deletes one. Use the name of a system as the system id, never personal data.
Find unpriced traffic
An exchange is unpriced when no price covers every token class that it used. It counts in the exchanges and tokens, and not in the spend. When a period has unpriced traffic, the answer states the cause: a served name with no price, or a token class with no rate. An exchange whose response names no served name, such as a refused request or an error response, counts as a served name with no price, and no price can cover it. Divide by model to see which served names have unpriced traffic. Publish a catalog version that adds the price or the missing rate.
Which unpriced traffic the new version prices depends on the cause:
-
A served name with no price. The service prices the recorded traffic of that served name that was served at or after the
effectiveAtof the new price. -
A token class with no rate. The exchange keeps the version that priced its served name, and that version has no rate for the class. The new rate applies only to traffic served after the new version takes effect and recorded after it is published. Traffic recorded before that remains unpriced.
To price the recorded traffic of a served name, publish the first price of that served name:
-
Set
effectiveAtat or before the earliest exchange. Only the first price of a served name can take effect that early, because each later price must take effect after the latest one. Once a served name has a price, traffic served before that price remains unpriced. -
Leave out the served names that have a price later than that
effectiveAt. TheeffectiveAtmust be later than the latest price of every served name that the document carries. -
Give the price a rate for every token class that the recorded traffic used. The service gives that traffic the version of the first price whether or not every rate is there. Traffic that used a class with no rate keeps that version and remains unpriced.
The service starts to price the recorded traffic shortly after the publish, and a long range takes longer. The service keeps the record of each exchange for at least 90 days. Older traffic keeps its figures and remains unpriced. The spend of a period can rise after a publish, and its unpriced count fall, also when the period is complete.
When a price changes during a period
A period that spans a price change holds exchanges priced at each rate. To see when each price took effect, run akka optimize pricing-catalog versions list. To read the prices at an instant, run akka optimize pricing-catalog get --at with that instant.
Compare two periods
akka optimize spend compare --from 2026-09-15T16:00:00Z --to 2026-09-15T17:00:00Z \
--against-from 2026-09-14T16:00:00Z --against-to 2026-09-14T17:00:00Z --by model
The answer gives the tokens and spend for each period, and the difference between them. A difference has a value only when both periods have one in the same currency.
Follow spend over time
spend series divides a period into UTC hours or UTC days. It answers each of them as spend get answers that hour or day alone, completeness included.
akka optimize spend series --from 2026-09-13T00:00:00Z --to 2026-09-15T00:00:00Z --granularity day --by model
BUCKET COMPLETENESS PERMANENCE MODEL EXCHANGES TOKENS SPEND UNPRICED UNPRICED TOKENS
2026-09-13T00:00:00Z complete reported frontier-model 1204 29800+ 0.4050000000000000 USD 0 0
2026-09-13T00:00:00Z complete reported finetune-model 610 14200+ 0.0026600000000000 USD 0 0
2026-09-13T00:00:00Z complete reported TOTAL 1814 44000+ 0.4076600000000000 USD 0 0
2026-09-14T00:00:00Z complete reported frontier-model 1188 29350+ 0.3977500000000000 USD 0 0
2026-09-14T00:00:00Z complete reported finetune-model 645 15020+ 0.0028060000000000 USD 0 0
2026-09-14T00:00:00Z complete reported TOTAL 1833 44370+ 0.4005560000000000 USD 0 0
--granularity is hour or day, and has no default. A day series starts and ends at midnight UTC. For days in another time zone, ask for hours and add them up. PERMANENCE is reported when the hour or day is complete and nothing more arrives for it, and working otherwise. A publish can still price the unpriced traffic of a reported hour or day. --by and --system work as they do for one period. A series holds at most 744 buckets by default, and the service refuses a longer one.
List the most recent exchanges
spend recent lists the most recent exchanges, newest first. --since is required. It is an ISO-8601 instant, and it is inclusive. The service refuses a --since before the start of its record period, and the refusal names the oldest instant that you can ask for.
akka optimize spend recent --since 2026-09-16T00:00:00Z --limit 3
SERVED AT EXCHANGE SYSTEM ID TAGS MODEL PROVIDER OUTCOME TOKENS SPEND
2026-09-16T00:04:12.481Z 01M2KREEJ184G7ZWYRD6Z1RV0W svc-support team=payments frontier-model example answered 212+ 0.0017000000000000 USD
2026-09-16T00:04:09.032Z 01M2KREB68355QAKGD2AVSQWH2 svc-search — — — refused 0+ — (no-price)
2026-09-16T00:03:58.770Z 01M2KRE15JF8QY40YGECYYS9E9 unidentified team=search frontier-model example answered 65+ 0.0008250000000000 USD
Each row is one exchange: when it was served, its exchange id, its system id, its tags, the model, the provider that the pricing catalog names for the model, its outcome, its tokens and its spend. TAGS holds every tag of the exchange, written KEY=VALUE, such as team=payments. --limit sets how many rows the command returns: 20 by default, and at most 100. --system lists the exchanges of one system id, and --tag KEY:VALUE lists the exchanges that carry that tag. With both flags, a row has to match both.
SPEND has no value when no price covers the exchange: — (no-price) when its served name has no price, and — (class-not-priced) when a token class it used has no rate. PROVIDER is — when its served name has no price.
OUTCOME is one of these:
-
answered: the upstream answered with a status from 200 to 299. -
refused: a gateway policy answered, and the request reached no upstream. -
failed: the upstream answered with a status outside 200 to 299, or the exchange ended with no answer recorded. A refusal by the provider, such as a 429, is a failure. -
unknown: nothing recorded an answer for the exchange.
An exchange appears only after the service has read its capture, so the newest exchanges may not be in the list yet.
Find the most expensive conversations
akka optimize spend conversations list --from 2026-09-15T00:00:00Z --to 2026-09-16T00:00:00Z
This is a ranking of conversations by spend, not a division: the rows do not add up to the period total. The service refuses a window longer than 24 hours by default, and the refusal names the longest window that you can ask for.
| The features described in this section are an add-on to Akka Automated Operations. They are not included in the base product. |