Passa al contenuto principale

L'API Usage & Cost Analytics: attribuire ogni dollaro

Avanzato
What you'll learn
  • Che cosa ha sostituito usage_report e cost_report, e perché la nuova forma è fatta di righe e non di bucket temporali
  • Il contratto delle righe: starting_at / ending_at, group e usage o cost — e perché una dimensione vale null a meno che tu non raggruppi per essa
  • group_by[] accetta cinque dimensioni — tranne le due dell'endpoint cost che non contano e non rispettano limit
  • Il modello degli actor: utente, service account, identità cloud federata o la API key stessa — e perché api_key_ids[] e actor_ids[] rispondono a domande diverse
  • La trappola dei centesimi: amount è una stringa decimale in CENTESIMI, quindi "41280.000000" sono $412,80 e una dashboard ingenua sbaglia di 100 volte
  • Le regole di freschezza dei dati che invalidano una dashboard in giornata, e il trucco per ottenere risultati che non cambiano tra una chiamata e l'altra
  • La ricetta in una riga per il costo di Claude Code per singolo utente

Due endpoint, un solo compito: dirti dove è finita davvero la tua spesa su Claude.

  • GET /v1/organizations/analytics/usage — conteggi di token e di richieste.
  • GET /v1/organizations/analytics/cost — costo in USD.

Per un'organizzazione Claude Console è più o meno ciò che mostrano le pagine Usage e Cost della Console, con le dimensioni che scegli tu invece di quelle scelte da loro. Il motivo per cui conta è la domanda a cui nessuno sa rispondere dalla Console: quale dei miei clienti, agent o API key è responsabile della riga che è salita?

Questo è reporting, non enforcement

Niente qui ferma una fattura. Per quello, vedi Hard Spend Caps — e nota che i due sono complementari: i cap decidono se la spesa avviene, questa API ti dice chi l'ha causata.

Credenziali, per tipo di organizzazione​

OrganizzazioneCosa usare
Claude ConsoleUna Admin API key, oppure un bearer token OAuth con lo scope org:admin (inviato come authorization: Bearer, non x-api-key)
Claude EnterpriseUna chiave con lo scope read:analytics, creata in claude.ai dal proprietario principale dopo l'abilitazione dell'Analytics API per l'organizzazione

Dai al servizio collector una chiave dedicata, tienila in un secrets manager e imposta una scadenza della chiave così la rotazione è imposta anziché affidata alla memoria. Dove disponibile, una regola Workload Identity Federation con org:admin è decisamente meglio — token di breve durata, nessun segreto di lunga durata da far trapelare. Stesso ragionamento dell'Admin API in generale.

Avvio rapido​

Utilizzo giornaliero di token per modello, una settimana

curl --globoff "https://api.anthropic.com/v1/organizations/analytics/usage?\
starting_at=2026-09-01T00:00:00Z&\
ending_at=2026-09-08T00:00:00Z&\
time_bucket_width=1d&\
group_by[]=model&\
limit=100" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: analytics-usage-cost-2026-08-24" \
-H "x-api-key: $ANTHROPIC_API_KEY"

--globoff è importante: senza di esso, curl cerca di interpretare i [] di group_by[] come un intervallo glob.

Il contratto delle righe​

Questa è la parte che fa inciampare chi migra dai vecchi endpoint, perché la risposta ha di proposito una forma diversa.

Guided walkthrough1 of 6
  1. starting_at e ending_at indicano il periodo coperto dalla riga, group contiene ogni dimensione, e usage (o cost sull'endpoint cost) contiene le metriche. Dimensioni e metriche stanno in oggetti separati — non sono affiancate come nel report legacy.

order_by sull'endpoint usage accetta total_tokens (default), output_tokens, uncached_input_tokens, requests o time. Sull'endpoint cost: amount (default), list_amount o time.

L'ordinamento per rango come default è il bug silenzioso delle dashboard

Se richiedi un intervallo e disegni le righe nell'ordine in cui arrivano, ottieni un grafico a barre ordinato per dimensione che sembra una serie temporale. Ottieni righe cronologiche solo se imposti entrambi time_bucket_width e order_by=time.

Non ogni dimensione si applica a te​

model e actor valgono per ogni organizzazione. api_key_id è solo Console. slack_channel_id è solo Enterprise. Una dimensione che non si applica vale null su ogni riga e il suo filtro non corrisponde a nulla — in silenzio. L'unica eccezione è rbac_group_id, che invece restituisce un errore quando i team non sono disponibili per la tua organizzazione.

E un cambio di comportamento da appendere al muro: nell'API legacy una API key o un ID workspace sconosciuti restituivano un errore. Qui non corrispondono a nulla, compreso un valore di models[] scritto male. Un risultato vuoto è ora la risposta normale a un refuso.

Gli actor: a chi viene addebitato​

La dimensione actor indica a chi viene attribuita una richiesta — un utente, un service account, un'identità cloud federata (un principal AWS o Google Cloud, per le organizzazioni che raggiungono Claude tramite la loro piattaforma cloud) oppure, quando nessuno di questi c'è dietro, la API key stessa. Raggruppa per actor e l'actor di ogni riga è un oggetto il cui type dice di che tipo è. L'utilizzo non attribuibile ad alcuna identità ha actor: null.

Le regole di mappatura:

  • Le chiavi personali e quelle dei service account agiscono come l'utente o il service account a cui sono collegate: l'identità compare in actor, la chiave in api_key_id.
  • Le chiavi workspace legacy non sono collegate a un'identità, quindi la chiave compare sia come actor sia come api_key_id.
  • Una chiave che Claude Code crea per un membro al momento dell'accesso vive nel workspace Claude Code, e il suo utilizzo è attribuito a quel membro.
  • Le richieste senza alcuna API key — dalla Console, da Claude Code dopo un accesso senza API key, o con un token Workload Identity Federation — hanno api_key_id: null. L'actor resta il membro che ha effettuato l'accesso o, per un workload federato, il suo service account.
Due filtri, due domande diverse

api_key_ids[]=apikey_… corrisponde a ogni richiesta autenticata da quella chiave, a chiunque sia attribuita. actor_ids[]=apikey_… corrisponde solo alle richieste attribuite alla chiave stessa. Non sono intercambiabili, e confonderli è il modo in cui un audit torna incompleto. Per includere in un filtro api_key_ids[] anche l'utilizzo senza chiave, aggiungi una voce vuota: api_key_ids[]=.

L'endpoint cost e la trappola del 100×​

L'endpoint cost accetta gli stessi filtri, dimensioni e bucket temporali di /usage. L'oggetto cost di ogni riga contiene:

CampoSignificato
amountCosto dopo gli sconti, prima dell'applicazione dei crediti
list_amountCosto ai prezzi di listino
currencySempre "USD"
requestsNumero di richieste
amount è in CENTESIMI, non in dollari

currency dice "USD", ma il numero è una stringa decimale in centesimi con sei cifre decimali. "41280.000000" sono $412,80, non $41.280. Il vecchio cost report restituiva stringhe quasi in dollari come "123.45"; lo stesso valore qui è "12345.000000". Dividi per 100 prima di mostrare qualsiasi cosa, e scrivi il test — è un errore di 100× che supera la code review perché il campo si chiama amount e il campo della valuta dice dollari.

Due dimensioni esistono solo sull'endpoint cost, ed entrambe infrangono a tuo favore le solite regole: group_by[]=cost_type divide ogni riga per tipo di costo e group_by[]=token_type divide il costo dei token per tipo di token. Nessuna delle due conta nel limite di cinque dimensioni, e nessuna rispetta limit — quindi una pagina può legittimamente contenere più righe di limit.

Dove sono finiti i soldi: costo per modello, tipo di costo e tipo di token in un mese

curl --globoff "https://api.anthropic.com/v1/organizations/analytics/cost?\
starting_at=2026-09-01T00:00:00Z&\
ending_at=2026-10-01T00:00:00Z&\
group_by[]=model&\
group_by[]=cost_type&\
group_by[]=token_type" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: analytics-usage-cost-2026-08-24" \
-H "x-api-key: $ANTHROPIC_API_KEY"

Quell'unica query è la prima che vale la pena eseguire su qualsiasi organizzazione, perché risponde alla domanda su cui la maggior parte dei team tira a indovinare: quanta parte della fattura non è affatto inferenza. Dividere per cost_type separa la spesa in token dagli strumenti lato server; dividere per token_type separa le letture dalla cache dall'input non in cache e dall'output. Se le letture dalla cache dominano la tua riga di input, le tappe successive sono Prompt Caching e Cache Diagnostics; se dominano i token di output, è Taglia l'uso dei token.

Costo di Claude Code per singolo utente, in una query

curl --globoff "https://api.anthropic.com/v1/organizations/analytics/cost?\
starting_at=2026-10-01T00:00:00Z&\
ending_at=2026-11-01T00:00:00Z&\
products[]=claude_code&\
group_by[]=actor" \
-H "anthropic-version: 2023-06-01" \
-H "anthropic-beta: analytics-usage-cost-2026-08-24" \
-H "x-api-key: $ANTHROPIC_API_KEY"

Le organizzazioni Console hanno una seconda opzione per questo scopo — la dedicata Claude Code Analytics API, che riporta l'attività di Claude Code per utente insieme al costo stimato.

Intervalli di date, e gli arrotondamenti che nessuno si aspetta​

  • I timestamp sono RFC 3339 con offset UTC (2026-09-01T00:00:00Z). starting_at è inclusivo, ending_at è esclusivo.
  • starting_at deve essere uguale o successivo al 1° gennaio 2026 ed entro gli ultimi 365 giorni.
  • Una richiesta copre al massimo 31 giorni. ending_at ha come default 31 giorni dopo starting_at, oppure adesso, quale dei due viene prima. Dividi gli intervalli più lunghi in più richieste.
  • ending_at è obbligatorio ogni volta che time_bucket_width è impostato, e con time_bucket_width=1m l'intervallo è al massimo di 24 ore.
  • Entrambi gli estremi si arrotondano per difetto al confine di un bucket — o all'ora quando time_bucket_width non è impostato. Con bucket 1h, starting_at=2026-09-01T10:43:00Z diventa 10:00:00Z e ending_at=2026-09-01T15:43:00Z diventa 15:00:00Z, quindi la risposta copre le 10:00–15:00 UTC. Una query "ultimi 90 minuti" scritta ingenuamente restituisce un'ora.

La freschezza dei dati è ciò che rompe le dashboard​

Ogni risposta porta un timestamp data_refreshed_at. L'utilizzo successivo a quell'istante è mancante o parziale. Quando non esistono ancora dati per alcuna parte dell'intervallo, data_refreshed_at è null e data è vuoto.

ProprietàValore
Ritardo tipico di aggiornamentoEntro 4 ore dall'utilizzo sottostante
Caso peggioreFino a 24 ore
Finestra di revisioneI valori possono cambiare man mano che arrivano eventi in ritardo e riconciliazioni, fino a circa 7 giorni dopo la fine del mese di calendario
PaginazioneOgni pagina di una stessa query paginata legge lo stesso snapshot, quindi data_refreshed_at è identico tra le pagine

In pratica: i numeri del 1° ottobre possono ancora cambiare fino a circa il 7 novembre.

È una vera regressione rispetto all'API legacy, dove i dati comparivano tipicamente entro 5 minuti, e cambia ciò che puoi costruire. Ne seguono direttamente due regole:

  • Per risultati che non cambiano tra una chiamata e l'altra, imposta ending_at a un valore pari o precedente a un data_refreshed_at che hai già visto.
  • Per i totali definitivi, interroga solo mesi terminati da almeno 7 giorni.
Non costruirci sopra alert in tempo reale

Un alert su un picco di costo con 4–24 ore di ritardo non è un alert. Usa l'API per attribuzione, chargeback e report di tendenza; usa gli spend cap per la parte che deve reagire in pochi minuti, e l'OpenTelemetry tracing per una visibilità per richiesta quasi in tempo reale — ricordando che le trace sono best-effort e at-least-once, quindi il registro contabile è l'Analytics API, non le tue trace.

Migrare da usage_report e cost_report​

Cosa resta uguale: la tua Admin API key, l'header anthropic-version, starting_at / ending_at, i valori di ampiezza dei bucket (1m, 1h, 1d), la notazione tra parentesi, i nomi dei campi di paginazione e i nomi dei campi di token e costo. Cosa cambia:

Usage & Cost API legacyUsage & Cost Analytics API
Nessun header betaanthropic-beta: analytics-usage-cost-2026-08-24 a ogni richiesta
Dati entro ~5 minuti4 ore tipiche, fino a 24, riviste per ~7 giorni dopo la fine del mese
data è un insieme di bucket temporali (dal più vecchio, compresi quelli vuoti), ciascuno con una lista results che mescola dimensioni e metrichedata è un insieme di righe ordinate per order_by, ciascuna con il proprio starting_at / ending_at, dimensioni in group, metriche in usage o cost. Nessuna riga per una combinazione vuota
bucket_width, default 1dtime_bucket_width, nessun default — passa 1d per mantenere le righe giornaliere, omettilo per i totali sull'intero intervallo
limit conta i bucket temporali per paginalimit conta le righe per pagina (1–1.000, default 20), quindi il numero di pagine dipende da quante combinazioni hanno dati
Qualsiasi intervallo di date, paginato in avanti per bucketstarting_at ≥ 1° gen 2026 ed entro 365 giorni; ≤ 31 giorni per richiesta; ending_at obbligatorio con i bucket; ≤ 24 h con 1m
account_ids[], service_account_ids[], group_by[]=account_id, group_by[]=service_account_idactor_ids[] con gli stessi ID con tag, e group_by[]=actor o actor_type; leggi actor.user_id o actor.service_account_id
context_window[]context_windows[] (ora al plurale)
group_by[]=description sul cost report, con una stringa descriptionScomparso. Raggruppa invece per dimensioni strutturate — model, cost_type, token_type, context_window, service_tier, inference_geo, speed, tutte contenibili in un'unica richiesta
API key o ID workspace sconosciuti → erroreNon corrisponde a nulla. Vale lo stesso per un valore di models[] scritto male
workspace_id: null indicava il workspace predefinitoIl workspace predefinito ha il suo ID wrkspc_ reale
inference_geo: "not_available", service_tier: "standard" per l'utilizzo senza nessuno dei duenull per entrambi. inference_geos[]=not_available corrisponde ancora alle righe senza geo
Il service_tier del costo era standard o batch; amount come "123.45"Il tier effettivo; amount con sei decimali ("123.450000"), più list_amount
Code execution assente dal report usage; nessun conteggio di richiesteCode execution compare come righe di utilizzo con model: null; requests su entrambi gli endpoint e total_tokens sulle righe usage

Per riprodurre esattamente il report giornaliero legacy: richiedi time_bucket_width=1d con gli stessi group_by[], scorri le pagine con limit=1000, aggiungi order_by=time&order=asc per avere i giorni in ordine cronologico, poi raggruppa le righe per starting_at e riempi tu i giorni mancanti con zeri.

Il workspace predefinito non è più null — aggiorna il tuo codice di raggruppamento

I consumer legacy spesso trattavano workspace_id: null come caso speciale "Default Workspace". Quella mappatura ora è sbagliata, e qui un null significa invece "non raggruppato per esso". Il workspace predefinito ha un normale ID wrkspc_ — ma List Workspaces lo omette a meno che tu non passi include_default=true, quindi un ID sconosciuto nelle tue righe potrebbe non comparire nella tua tabella dei workspace. Risolvilo con Get Workspace: quello predefinito restituisce "name": "Default".

Oltre la Claude API​

PiattaformaDisponibilitàCosa cambia
Claude Platform on AWSEntrambi gli endpointUsa l'URL base e l'autenticazione della piattaforma al posto di una Admin API key; AWS IAM autorizza tramite le azioni ListAccountUsage e ListAccountCost. Ogni richiesta copre solo i workspace della regione AWS di quell'endpoint — chiama ogni regione per coprire l'organizzazione. user_profile_id, claude_tag_category, claude_tag_user_id e le dimensioni di Managed Agents (agent_id, environment_id, deployment_id, session_id) sono null su ogni riga
Claude Models on Google Cloud (gestito da Anthropic)Entrambi gli endpointChiamato tramite l'URL base a livello di organizzazione con credenziali Google Cloud; /usage richiede il permesso IAM usageAnalytics.list, /cost richiede costAnalytics.list

Quella dimensione user_profile_id è la più interessante sulla Claude API stessa: combinata con i profili utente, trasforma questa API in un chargeback per cliente finale per una piattaforma o un rivenditore — la stessa attribuzione che limita il raggio d'azione del tuo enforcement ti dice anche quanto ti costa ciascun cliente.

Errori comuni​

ErroreCosa succede davvero
Mostrare amount come dollariOgni cifra è 100× troppo grande. Sono centesimi con sei cifre decimali
Disegnare le righe nell'ordine di risposta come serie temporaleLe righe sono ordinate per order_by sull'intero intervallo. Servono time_bucket_width e order_by=time
Omettere --globoff da curlcurl tratta [] in group_by[] come un glob e la richiesta non parte come scritta
Aspettarsi bucket riempiti con zeriI bucket e le combinazioni senza dati producono nessuna riga. Riempi i vuoti lato client
Costruirci un alert sui picchi di costoI dati hanno 4–24 ore di ritardo e vengono rivisti per una settimana dopo la fine del mese
Confrontare totali da inizio mese estratti in giorni diversiI valori vengono rivisti fino a ~7 giorni dopo la fine del mese. Ancora ending_at a un data_refreshed_at che hai già visto
Trattare un risultato vuoto come "nessun utilizzo"Un ID workspace, un ID API key o un nome di modello scritto male sconosciuti ora non corrispondono a nulla invece di dare errore
Mantenere la mappatura workspace_id: null → "Default"Il workspace predefinito ha ora un vero ID wrkspc_; null significa "non raggruppato per questa dimensione"
Presumere che limit limiti le righe per paginacost_type e token_type ignorano del tutto limit
Riutilizzare account_ids[] / service_account_ids[]Sostituiti da actor_ids[] con group_by[]=actor o actor_type

Quiz​

Check yourself

0/3
  1. Una riga di costo torna con cost.amount = "41280.000000" e cost.currency = "USD". Che cosa dovrebbe mostrare la tua dashboard?
  2. Richiedi 7 giorni di utilizzo con group_by[]=model e disegni le righe nell'ordine in cui arrivano, aspettandoti un grafico a linee giornaliero. Ottieni qualcosa che non è cronologico. Che cosa manca?
  3. L'amministrazione estrae il costo da inizio mese di ottobre il 2 ottobre, di nuovo il 20 ottobre e ancora il 10 novembre — e ottiene tre totali diversi per il 1° ottobre. Quale spiegazione corrisponde al comportamento documentato?
Key takeaways
  • Due endpoint — /v1/organizations/analytics/usage e /cost — che richiedono entrambi anthropic-beta: analytics-usage-cost-2026-08-24.
  • Dividi amount per 100. Sono centesimi con sei cifre decimali, nonostante currency riporti USD.
  • Una dimensione è null a meno che tu non raggruppi per essa, e una combinazione senza dati non produce alcuna riga — quindi riempi con zeri lato client.
  • Ordinamento per rango come default: una vera serie temporale richiede time_bucket_width E order_by=time.
  • La freschezza è di 4-24 ore con revisioni per ~7 giorni dopo la fine del mese. È infrastruttura per attribuzione e chargeback, non per gli alert.
  • api_key_ids[] (ciò che la chiave ha autenticato) e actor_ids[] (ciò che è attribuito alla chiave) rispondono a domande diverse — scegli con cognizione.
  • I refusi non danno più errore. Un workspace, una chiave o un nome di modello sconosciuti non corrispondono a nulla, in silenzio.

Avanti​