Apuração de custos
Um valor de custo só vale a pena se você consegue dizer de onde ele veio. Aqui está cada passo entre uma contagem de tokens e um número na tela.
Precificado uma vez, na ingestão
Quando um span chega trazendo um id de modelo e uma contagem de tokens, o mcpd procura esse modelo em uma tabela de preços, calcula o que a chamada custou e guarda o resultado no span. Nada é recalculado na leitura.
Essa é a decisão importante desta página. Preços mudam. Se o custo fosse derivado na hora da consulta, a partir da tabela que estivesse carregada naquele dia, um corte de tarifa no mês que vem reescreveria em silêncio o que o mês passado custou, e o número que você informou a alguém não estaria lá quando essa pessoa fosse conferir. Precificar na ingestão congela o valor contra as tarifas que valiam quando a chamada foi feita. Todo custo armazenado também registra o id da tabela que o produziu, então um valor pode ser rastreado até as tarifas que estão por trás dele.
Um span que já chega com um custo é deixado exatamente como chegou. Se o seu
provedor entrega um valor real e você o registra, o mcpd mantém o seu e não
precifica por cima. O campo is_estimated separa os dois: tudo o
que o mcpd derivou de uma tabela é marcado como estimado, porque é.
As tarifas são por milhão de tokens
Toda tarifa em uma tabela de preços é em unidades da moeda por milhão de
tokens, que é como todo provedor as publica. Uma tarifa copiada de uma
página de preços entra na tabela sem mudança, sem nenhuma divisão para você
errar às três da manhã. A aritmética de um medidor é
tokens × tarifa ÷ 1.000.000.
O custo fica na etapa, não na execução
Cada chamada de modelo carrega o próprio valor. O total de uma execução é a soma dos spans dela, e o mcpd recalcula essa soma a partir dos spans toda vez que um deles chega, em vez de incrementar um contador. Spans chegam fora de ordem, são reenviados e são atualizados no lugar, e um total que só incrementa desanda nos três casos. Um número de destaque que não bate com as linhas abaixo dele destrói a confiança em todos os outros números da página.
- agent support_agent fail
- model chat
- tool returns_policy
- model chat
- tool returns_policy
- model chat
- tool returns_policy
The model called one tool three times and never answered. That is visible in four seconds here, and invisible in a log file.
As três chamadas de modelo acima estão precificadas em $0.00015, $0.00018 e $0.00021. O total da execução no cabeçalho é a soma delas e nada mais: as chamadas de ferramenta, a consulta à memória e o guardrail não consumiram tokens, então não carregam custo e não contribuem com nada.
Os medidores
Uma chamada de modelo não é um número só de tokens. Provedores cobram vários medidores a tarifas diferentes, e uma tabela de preços dá uma tarifa para cada um deles.
| Tarifa | Vale para | Quando a tabela a omite |
|---|---|---|
input | Tokens de entrada comuns. | Obrigatória. |
output | Tokens de resposta comuns. | Obrigatória. |
cache_read | Tokens servidos de um cache de prompt. | Cobrados como input. |
cache_write | Tokens gravados em um cache de prompt. | Cobrados como input. |
cache_write_5m | Gravações em um cache de cinco minutos, onde o provedor cobra os TTLs à parte. | Recua para cache_write, depois para input. |
cache_write_1h | Gravações em um cache de uma hora. | Recua para cache_write, depois para input. |
audio_input | Tokens de áudio de entrada. | Cobrados como input. |
audio_output | Tokens de áudio de saída. | Cobrados como output. |
reasoning | Tokens de raciocínio ocultos, onde um provedor os cobra à parte. |
Sem recuo. Eles são cobrados como output, dentro do total
de saída, que é como os grandes provedores os cobram, a menos que o
provedor também tenha informado uma contagem text
separada. Veja abaixo.
|
Um token em cache é cobrado uma vez
O jeito clássico de inflar uma conta é cobrar um token em cache duas vezes: uma na tarifa de cache, e de novo como parte do total de entrada informado, dentro do qual ele já está. Então, quando um provedor informa um detalhamento por medidor, esse detalhamento vence. O mcpd precifica cada medidor na tarifa dele e cobra apenas o restante na tarifa de entrada comum.
Uma chamada a claude-sonnet-4 informando um milhão de tokens de
entrada, dos quais novecentos mil foram lidos do cache, é precificada contra
a tabela embutida assim:
- 900.000 leituras de cache a $0.30 por milhão, o que dá $0.27.
- 100.000 tokens de entrada restantes a $3.00 por milhão, o que dá $0.30.
- $0.57 de custo de entrada, em vez dos $3.00 que o total informado, sozinho, teria produzido.
Onde um provedor dá uma contagem text explícita, essa contagem
é usada diretamente como o restante. Onde ele não dá, o restante é o total
informado menos todos os campos com medidor.
Isso deixa uma lacuna que vale nomear. Um medidor para o qual a tabela não
tem tarifa só é cobrado como parte do restante, então, quando o provedor
também mandou uma contagem text, ele não é cobrado de jeito
nenhum. Tokens de raciocínio caem aí sempre que a sua tabela não lhes dá uma
tarifa própria, e declarar uma resolve isso. Tokens de imagem caem aí
sempre, porque uma tabela não tem tarifa de imagem para dar, então, em uma
carga de trabalho bastante multimodal, leia o valor como baixo, e não como exato.
Um modelo desconhecido não tem custo, e não um custo de zero
Se a tabela não tem tarifas para o modelo, o mcpd guarda o span sem campo de custo nenhum. Ele não guarda um zero.
Zero é uma afirmação: esta chamada foi gratuita. Ausente é uma admissão: não sabemos o que esta chamada custou. As duas levam a decisões diferentes, e uma ferramenta que informa a segunda como se fosse a primeira está mentindo sobre uma conta. A mesma regra atravessa o caminho de leitura, onde uma execução cujos spans somam nada é devolvida sem valor de custo, em vez de com um custo de zero, então uma coluna de custo em branco quer dizer não precificado e nunca gratuito.
Vale internalizar a consequência prática: um modelo que falta na tabela
falta em todo total. O gasto dele não aparece em
/v1/api/analytics/cost, e as execuções dele são excluídas por
qualquer filtro min_cost positivo. Um total semanal que parece
baixo demais costuma ser um id de modelo que não está resolvendo, e não uma
semana fraca.
Como um id de modelo é resolvido
O id em um span é o que o seu cliente enviou, e clientes enfeitam esse id. Um gateway prefixa o fornecedor dele, uma nuvem prefixa a região dela, um provedor fixa uma data de lançamento. Uma tabela é indexada por nomes de modelo puros, então a busca tenta quatro coisas em ordem e para na primeira que acerta.
| Passo | Exemplo |
|---|---|
| O id como veio, sem diferenciar maiúsculas | GPT-4o encontra gpt-4o |
| A parte depois da última barra | openai/gpt-4o encontra gpt-4o |
| O final com pontos de um id de nuvem | us.anthropic.claude-sonnet-4 encontra
claude-sonnet-4 |
| O id com a data do fim removida, com ou sem o prefixo | openai/gpt-4o-2024-08-06 encontra gpt-4o |
Os sufixos -YYYY-MM-DD e -YYYYMMDD são os dois
reconhecidos. Falhar nos quatro passos é um desfecho normal, e não um erro:
quer dizer nenhum custo, como acima.
A tabela que vem junto
O mcpd carrega uma pequena tabela embutida para que um docker run
produza valores reais sem configuração nenhuma. Ela se chama
builtin, precifica em USD, e todo custo que ela produz registra
esse nome.
| Modelo | Entrada | Saída | Leitura de cache | Gravação de cache, 5 min | Gravação de cache, 1 hora |
|---|---|---|---|---|---|
claude-opus-4 | 15.00 | 75.00 | 1.50 | 18.75 | 30.00 |
claude-opus-4-1 | 15.00 | 75.00 | 1.50 | 18.75 | 30.00 |
claude-opus-4-5 | 5.00 | 25.00 | 0.50 | 6.25 | 10.00 |
claude-sonnet-4 | 3.00 | 15.00 | 0.30 | 3.75 | 6.00 |
claude-sonnet-4-5 | 3.00 | 15.00 | 0.30 | 3.75 | 6.00 |
claude-haiku-4-5 | 1.00 | 5.00 | 0.10 | 1.25 | 2.00 |
claude-3-5-haiku | 0.80 | 4.00 | 0.08 | 1.00 | 1.60 |
gpt-4o | 2.50 | 10.00 | 1.25 | como input | como input |
gpt-4o-mini | 0.15 | 0.60 | 0.075 | como input | como input |
gpt-4.1 | 2.00 | 8.00 | 0.50 | como input | como input |
gpt-4.1-mini | 0.40 | 1.60 | 0.10 | como input | como input |
o3 | 2.00 | 8.00 | 0.50 | como input | como input |
o4-mini | 1.10 | 4.40 | 0.275 | como input | como input |
gemini-2.5-pro | 1.25 | 10.00 | 0.31 | como input | como input |
gemini-2.5-flash | 0.30 | 2.50 | 0.075 | como input | como input |
text-embedding-3-small | 0.02 | 0.00 | como input | como input | como input |
text-embedding-3-large | 0.13 | 0.00 | como input | como input | como input |
USD por milhão de tokens, como a tabela estava na última vez em que foi
conferida contra um catálogo de modelos de terceiros, o que a tabela registra
como a data as_of dela. As entradas da Anthropic trazem as duas
tarifas de gravação de cache porque o daemon recua da tarifa de uma hora para
a de cinco minutos, e uma tabela que omitisse a mais longa cobraria a menos
em silêncio. A OpenAI não cobra nada para gravar no cache dela, então uma
gravação de cache ali é cobrada na tarifa de entrada. O
gemini-2.5-flash também traz uma tarifa de áudio de entrada de
1.00; toda outra entrada precifica áudio na tarifa de texto dela. Não existe
claude-haiku-4: o id que a Anthropic serve é
claude-haiku-4-5, e um id que não resolve para nada fica sem
preço em vez de adivinhado.
Estes são preços de tabela publicados, não os seus preços. Se você negociou tarifas, ou roda um modelo que não está nesta tabela, os valores que o mcpd mostra são uma estimativa e devem ser lidos como tal. A tabela é curta de propósito: uma tabela longa envelheceria em mais lugares e passaria uma impressão de exatidão maior do que ela merece.
Fornecer a sua própria tabela ainda não tem uma opção de linha de comando. A
tabela faz parte do estado do servidor, e o jeito de trocá-la hoje é embutir
o mcpd como biblioteca e passar uma para
AppState::with_price_book. Uma tabela é um id, uma moeda ISO
4217 e um mapa de id de modelo para tarifas, e todo valor que ela produz é
carimbado com o id dela, para que duas tabelas possam ser distinguidas depois
do fato. Os preços de um mesmo projeto deveriam compartilhar uma moeda; o
mcpd soma os spans de uma execução aritmeticamente e não converte entre
moedas.
Ler o dinheiro de volta
O GET /v1/api/analytics/cost responde para onde o dinheiro foi
ao longo de uma janela, sem exportar nada para uma planilha.
curl -s 'http://localhost:4317/v1/api/analytics/cost?from=-7d&group_by=model' | Parâmetro | Significado |
|---|---|
from, to | A janela. RFC 3339 ou a forma relativa abreviada. O padrão são os últimos sete dias, terminando agora. |
group_by | model por padrão. Também provider,
entity_name, environment,
status, session_id, user_id,
service_name ou name.
|
bucket | 30s, 5m, 1h ou 1d.
Omita para ter só os totais; informe para ter uma série temporal ao
lado deles.
|
project | Qual projeto ler. O padrão é o projeto padrão do daemon. |
para onde foi
# Where the money went last week, by model.
curl -s --get http://localhost:4317/v1/api/analytics/cost \
--data-urlencode 'from=-7d' \
--data-urlencode 'group_by=model'
# The same week as a daily series, so you can see the day it changed.
curl -s --get http://localhost:4317/v1/api/analytics/cost \
--data-urlencode 'from=-7d' \
--data-urlencode 'group_by=model' \
--data-urlencode 'bucket=1d' import { Mcpd } from '@mcpd/sdk'
const mcpd = new Mcpd()
const spend = await mcpd.analyticsCost({
from: '-7d',
to: '-0s',
groupBy: 'model',
bucket: '1d',
})
console.log(`${spend.currency} ${spend.totalCost.toFixed(4)} over the window`)
for (const group of spend.groups) {
console.log(group.key, group.totalCost, group.inputTokens, group.outputTokens)
} from mcpd import Mcpd
mcpd = Mcpd()
spend = mcpd.analytics_cost("-7d", "-0s", group_by="model", bucket="1d")
print(f"{spend.currency} {spend.total_cost:.4f} over the window")
for group in spend.groups:
print(group.key, group.total_cost, group.input_tokens, group.output_tokens) Os grupos voltam do maior para o menor, limitados a cem linhas, e uma série por bucket é limitada a cinco mil pontos.
resposta
{
"from": "2026-08-20T09:00:00Z",
"to": "2026-08-27T09:00:00Z",
"currency": "USD",
"total_cost": 12.418264,
"groups": [
{
"key": "claude-sonnet-4",
"count": 1841,
"error_count": 12,
"total_cost": 9.612055,
"input_tokens": 2903118,
"output_tokens": 214477,
"latency": { "mean": 1840.2, "max": 24193.0 }
},
{
"key": "gpt-4o-mini",
"count": 6204,
"error_count": 3,
"total_cost": 2.806209,
"input_tokens": 9114882,
"output_tokens": 388310,
"latency": { "mean": 612.4, "max": 8801.0 }
}
]
}
Uma sutileza no count. Agrupar por model,
provider ou entity_name lê os spans, então
count é um número de chamadas de modelo. Agrupar por qualquer
outra coisa lê a projeção da execução, então count é um número
de execuções. Os dois são úteis; eles não são o mesmo número, e comparar um
com o outro é o erro mais fácil de cometer aqui.
A latência por grupo nesta resposta traz apenas uma média e um máximo. Os
percentis vêm do
GET /v1/api/analytics/summary, que os calcula sobre a janela.
Para onde ir depois
O custo é uma coluna em uma lista de execuções, e a lista de execuções é
onde uma investigação de fato começa:
Encontrar uma execução
cobre o min_cost e a ordenação por gasto. Se os seus valores
parecem errados porque payloads estão sendo descartados antes de serem
precificados, veja
Amostragem, que
decide o que é registrado. Todo campo citado nesta página está listado no
modelo de dados.