Tracking inference spend

Feature set: Inference Contact our support for access.

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_URL environment variable to the address of the portfolio service. Your Akka team provides it. The --url flag does not apply to these commands, and --region is 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

effectiveAt

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 effectiveAt is in the past, that price also prices the recorded traffic of the served name from that instant on. A later price with a past effectiveAt does not change traffic that an earlier price covered.

source

Optional. Free text that records where the prices came from.

model

Required. The provider, name, and version of the model.

servedName

Required. The served name that this price applies to. A served name can appear once in a document.

assigned

Optional. true when your organization set the rate, for example a negotiated rate. false for a list price. Defaults to false.

currency

Required. An upper-case ISO 4217 code, such as USD. The catalog holds one currency. A document that introduces a second currency is refused.

input, output

Required. The rate per million input and output tokens.

cacheRead, cacheWrite, reasoning

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 servedName must be the string that your provider’s responses report in their model field. OpenAI responses report the dated snapshot, such as gpt-5.4-mini-2026-03-17, also when the request names gpt-5.4-mini. To see the served names that your traffic reports, divide a recent period by model, as in Read spend for a period. Add an entry for each served name in that division that has no price yet.

  • Set effectiveAt to the instant from which the prices apply, and source to where they came from. To price traffic that is already recorded, set effectiveAt at 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 assigned to true.

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.

  1. Write the prices in force to a file:

    akka optimize pricing-catalog get -o json > catalog.json
  2. 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.

  3. Run akka optimize pricing-catalog versions list, and look for a version that takes effect after the effectiveAt in the file. get shows 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.

  4. Set effectiveAt to an instant later than the effectiveAt in the file.

  5. Check the document:

    akka optimize pricing-catalog apply -f catalog.json --dry-run
  6. 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

none

No division. The answer is the period total, as when you leave out --by.

model

Served name.

useCase

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

System id that the request named, and one row, unidentified, for traffic that named none. See Attribute spend to systems.

route

Route that the gateway router took to the destination of the exchange.

tag:KEY

Value that the tagging ruleset KEY gave the exchange, and one row for traffic that the ruleset did not decide for. See Divide spend by a tag.

A comma-separated list, such as model,useCase or system,route

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

rule

A rule chose the destination, with or without a classifier.

fallback

The classifier of a rule failed, and the fallback of the rule chose the destination.

default

No rule in the ruleset matched, and the default destination of the ruleset applied.

manifest

No rule matched, and the use case manifest chose the destination.

denied

A rule refused the request. There is no destination.

failed

The ruleset could not run.

none

No routing ruleset ran.

unknown

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 effectiveAt of 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 effectiveAt at 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. The effectiveAt must 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.