API di fatturazione
Porta l'uso e il costo del tuo workspace nei tuoi sistemi, senza aprire la console. Una chiave di fatturazione è una di sola lettura credenziale limitata a un solo workspace - può solo leggere i dati di fatturazione, nulla di più.
Una chiave di fatturazione può leggere ogni richiesta registrata nel suo workspace, incluse le chiavi eliminate o revocate nel frattempo, ma non può chiamare alcun modello, né creare, modificare o eliminare alcuna chiave API. Usarla altrove restituisce 401 wrong_key_kind.
Creare una chiave di fatturazione
- Apri Console → Chiavi API di fatturazione (solo proprietario del workspace). Assegnale un nome, limitala facoltativamente a una whitelist di IP o imposta una scadenza, poi copia la chiave - inizia con
sk-syn-bill-e viene mostrata solo una volta. - Chiama gli endpoint sottostanti usandola come token Bearer. Revocala in qualsiasi momento dalla stessa pagina - la revoca la invalida immediatamente e non influisce su nulla altro nel workspace.
- Una chiave è legata a chi l'ha creata: se questa persona non è più il proprietario del workspace, smette di funzionare e restituisce
403 key_owner_not_authorized.
Autenticazione
Ogni chiamata richiede Authorization: Bearer sk-syn-bill-... verso l'URL di base qui sotto. Una chiave di fatturazione funziona solo su questi endpoint, e questi endpoint accettano solo una chiave di fatturazione - qualsiasi altra combinazione restituisce 401 wrong_key_kind.
Authorization: Bearer sk-syn-bill-... URL di base: https://synthorai.io/api/v1/billing
L'ambito è l'intero workspace: ogni chiave di inferenza al suo interno, incluse quelle eliminate - le loro righe storiche restano interrogabili. api_key_id e model possono solo restringere il risultato, mai ampliarlo; ogni query resta ancorata al workspace proprio della chiave.
Ogni query viene eseguita sulla replica di lettura, mai sulla primaria. Un processo senza replica configurata risponde 503 replica_unavailable invece di ricorrervi silenziosamente.
Campi dei token: seguono la convenzione OpenAI - prompt_tokens è il totale dei token di input e include già ogni lettura e scrittura di cache elencata sotto, e completion_tokens include già reasoning_tokens. Nessuno dei due si somma ai totali - sono una scomposizione, non token aggiuntivi.
La parte di lettura cache di prompt_tokens è cached_tokens; la parte di scrittura cache è cache_write_tokens, ulteriormente divisa nelle righe di /records in cache_write_5m_tokens e cache_write_1h_tokens in base al TTL.
Campi monetari: cost_usd è l'importo effettivamente addebitato. byok_list_price_usd è ciò che una richiesta BYOK avrebbe pagato al prezzo di listino - 0 per una richiesta non BYOK. Non esiste list_price_usd in nessun punto di questa API.
Qui "errore" indica qualsiasi richiesta che ha raggiunto l'API ed è stata rifiutata - un 4xx come 402 (saldo insufficiente), 403 o 429, così come un errore upstream. Sono escluse solo le reiezioni della nostra infrastruttura interna, esattamente come in console. I rifiuti vengono registrati al massimo una volta per chiave, codice di stato e minuto su ogni server, quindi i conteggi di 402, 403 e 429 sono un minimo; le richieste riuscite e gli addebiti sono completi.
GET /usage - utilizzo aggregato
GET /usage
Righe pre-aggregate per ora o per giorno, pensate per fatturazione e dashboard. Provengono dall'aggregato orario più le richieste scritte dall'ultima ingestione, così i bucket recenti non restano indietro di un intero ciclo di ingestione.
| Parametro | Tipo | Descrizione |
|---|---|---|
start* | string | Inizio dell'intervallo: un timestamp RFC 3339 o una data come 2026-09-01. |
end | string | Fine dell'intervallo. Il valore predefinito è ora. |
granularity | string | Dimensione del bucket: hour o day. Il valore predefinito è day. |
group_by | string | Dimensioni per scomporre le righe: api_key, model, o entrambe (qualsiasi sottoinsieme). Il valore predefinito è entrambe. |
api_key_id | integer | Filtra per uno o più id di chiave API. Ripetibile; restringe solo, non amplia mai. |
model | string | Filtra per uno o più id di modello. Ripetibile. |
limit | integer | Righe per pagina. Il valore predefinito è 1000, con un limite massimo di 5000. |
cursor | string | Cursore di paginazione opaco, ottenuto dal meta.next_cursor della pagina precedente. |
In ogni riga, api_key_id e api_key_name compaiono solo quando group_by include api_key, e model solo quando include model. avg_latency_ms può essere null quando un bucket non ha campioni di latenza.
Esempio di richiesta
curl "https://synthorai.io/api/v1/billing/usage?start=2026-09-01&end=2026-09-24&granularity=day" \
-H "Authorization: Bearer sk-syn-bill-..." Esempio di risposta
{
"data": [
{
"bucket_start": "2026-09-23T00:00:00Z",
"bucket_end": "2026-09-24T00:00:00Z",
"api_key_id": 42,
"api_key_name": "prod-backend",
"model": "claude-sonnet-5",
"requests": 1280,
"success_requests": 1265,
"error_requests": 15,
"errors_4xx": 12,
"errors_429": 2,
"errors_5xx": 1,
"prompt_tokens": 512000,
"completion_tokens": 98000,
"cached_tokens": 210000,
"cache_write_tokens": 8000,
"reasoning_tokens": 4000,
"cost_usd": 4.1732,
"byok_list_price_usd": 0,
"byok_requests": 0,
"avg_latency_ms": 812,
"final": true
}
],
"meta": {
"generated_at": "2026-09-24T08:00:00Z",
"data_complete_until": "2026-09-24T06:00:00Z",
"timezone": "UTC",
"currency": "USD",
"next_cursor": null,
"has_more": false
}
} GET /records - righe per richiesta
GET /records
Una riga per richiesta, pensata per la sincronizzazione incrementale nel tuo database. Sincronizza per cursor, mai per tempo, e deduplica le righe ricevute in base a record_id - vedi "Evitare i vuoti temporali" più sotto per il motivo.
| Parametro | Tipo | Descrizione |
|---|---|---|
cursor | string | Cursore di sincronizzazione opaco (stringa), ottenuto dal meta.next_cursor della chiamata precedente. Fornisci esattamente uno tra cursor o start; non ha alcun limite di intervallo. |
start | string | Inizio dell'intervallo (timestamp RFC 3339 o data). Fornisci esattamente uno tra cursor o start; un intervallo start/end è limitato a 31 giorni, mentre cursor non ha alcun limite di intervallo. |
end | string | Fine dell'intervallo. Il valore predefinito è ora. |
api_key_id | integer | Filtra per uno o più id di chiave API. Ripetibile; restringe solo, non amplia mai. |
model | string | Filtra per uno o più id di modello. Ripetibile. |
limit | integer | Righe per pagina. Il valore predefinito è 500, con un limite massimo di 1000. |
Esempio di richiesta
curl "https://synthorai.io/api/v1/billing/records?start=2026-09-24T00:00:00Z&limit=500" \
-H "Authorization: Bearer sk-syn-bill-..." Esempio di risposta
{
"data": [
{
"record_id": "rec_8f2a91c4",
"request_id": "req_9f3c2b1a",
"recorded_at": "2026-09-24T05:58:31Z",
"completed_at": "2026-09-24T05:58:29Z",
"api_key_id": 42,
"api_key_name": "prod-backend",
"model": "claude-sonnet-5",
"status": "success",
"status_code": 200,
"prompt_tokens": 812,
"completion_tokens": 194,
"cached_tokens": 512,
"cache_write_tokens": 64,
"cache_write_5m_tokens": 64,
"cache_write_1h_tokens": 0,
"cost_usd": 0.00412,
"byok_list_price_usd": 0,
"is_byok": false,
"is_stream": true,
"duration_ms": 1340,
"ttft_ms": 210
}
],
"meta": {
"next_cursor": "eyJvIjoiODgxNDA5MCJ9",
"has_more": false,
"window_final": true,
"safe_until": "2026-09-24T05:58:31Z",
"latest_recorded_at": "2026-09-24T05:58:31Z",
"data_complete_until": "2026-09-24T05:58:00Z"
}
} | Parametro | Descrizione |
|---|---|
next_cursor | Cursore per la chiamata successiva. Sempre presente. |
has_more | Indica se richiamare subito con questo cursore restituirebbe altre righe. |
window_final | true solo se hai fornito un end ed è precedente o uguale a safe_until - in tal caso l'intervallo richiesto è garantito completo. Assente o false altrimenti. |
safe_until | Il recorded_at più recente che un chiamante può considerare definitivo; le righe più recenti di questo sono ancora trattenute. |
latest_recorded_at | Il recorded_at più recente tra tutte le righe, definitive o no. |
data_complete_until | Lo stesso valore restituito da /freshness - fino a dove il rollup orario è completo, per un confronto con /usage. |
Base temporale: /usage raggruppa per completed_at (quando la richiesta è terminata). Il filtro start/end di /records si basa su recorded_at (quando la riga è stata scritta, che sotto carico può ritardare rispetto al completamento). Per riconciliare /records con /usage, raggruppa tu stesso le righe sincronizzate per completed_at, non per recorded_at.
GET /records/export - esportazione in streaming
GET /records/export
Gli stessi filtri di /records, trasmessi in streaming come JSON delimitato da a-capo (application/x-ndjson) - pensato per un grande backfill invece di un elenco paginato. Per ogni workspace può essere in streaming una sola esportazione alla volta; una seconda riceve 429 too_many_concurrent_requests.
Esempio di richiesta
curl -N "https://synthorai.io/api/v1/billing/records/export?cursor=eyJvIjoiODgxNDAzMiJ9" \
-H "Authorization: Bearer sk-syn-bill-..." Esempio di risposta
{"record_id":"rec_8f2a91c4","request_id":"req_9f3c2b1a","model":"claude-sonnet-5","status":"success","cost_usd":0.00412}
{"record_id":"rec_8f2a91c5","request_id":"req_9f3c2b1b","model":"claude-sonnet-5","status":"success","cost_usd":0.00398}
{"_meta":{"next_cursor":"eyJvIjoiODgxNDA5MSJ9","rows":2,"complete":true,"window_final":true,"generated_at":"2026-09-24T06:00:02Z"}} L'ultima riga dello stream è un _meta oggetto:
| Parametro | Descrizione |
|---|---|
next_cursor | Cursore per la successiva chiamata di esportazione. |
rows | Numero di record scritti in questo stream. |
complete | false significa che lo stream si è interrotto prima di esaurire l'intervallo - riprendi con cursor=next_cursor. |
window_final | Stesso significato di /records: true solo se l'end dell'esportazione è precedente o uguale a safe_until. |
generated_at | Momento in cui è stata scritta questa riga. |
stopped_because | Presente quando complete è false: row_cap, scan_cap (lo stream ha percorso il suo intervallo massimo di id senza riempirsi - frequente con un filtro stretto), time_cap, query_error o window_not_final. |
GET /summary - statistiche preliminari e anomalie
GET /summary
Totali, i modelli e le chiavi principali per costo, e un tasso di errore per l'intervallo, più una anomalies elenco (high_error_rate, rate_limited, data_not_final, rollup_delayed), ciascuna con una gravità info o warning e un messaggio leggibile. I numeri di un intervallo non ancora finalizzato possono ancora cambiare - vedi sotto.
| Parametro | Tipo | Descrizione |
|---|---|---|
start | string | Inizio dell'intervallo. Il valore predefinito è 30 giorni prima di end. |
end | string | Fine dell'intervallo. Il valore predefinito è ora. |
api_key_id | integer | Filtra per uno o più id di chiave API. Ripetibile; restringe solo, non amplia mai. |
model | string | Filtra per uno o più id di modello. Ripetibile. |
models_count e keys_count forniscono i totali dietro top_models e top_keys, che elencano al massimo i primi 20 per costo. L'intervallo predefinito è i 30 giorni prima di end quando start viene omesso.
Esempio di richiesta
curl "https://synthorai.io/api/v1/billing/summary?start=2026-09-17&end=2026-09-24" \
-H "Authorization: Bearer sk-syn-bill-..." Esempio di risposta
{
"data": {
"totals": { "requests": 48210, "cost_usd": 132.55, "error_rate": 0.021 },
"models_count": 6,
"keys_count": 3,
"top_models": [ { "model": "claude-sonnet-5", "cost_usd": 88.10, "byok_list_price_usd": 0 } ],
"top_keys": [ { "api_key_id": 42, "api_key_name": "prod-backend", "cost_usd": 61.30, "byok_list_price_usd": 0 } ],
"anomalies": [
{ "type": "data_not_final", "severity": "info", "message": "The last 2 hours of this range are not yet final." }
]
},
"meta": {
"generated_at": "2026-09-24T06:00:02Z",
"data_complete_until": "2026-09-24T04:00:00Z"
}
} GET /freshness - quanto sono completi i dati
GET /freshness
Restituisce data_complete_until, latest_recorded_at, safe_until e pipeline_lag_seconds - interrogalo prima di considerare definitivo un intervallo appena recuperato. safe_until è il recorded_at più recente che un chiamante può considerare completo; righe più recenti potrebbero essere ancora in arrivo.
Esempio di richiesta
curl "https://synthorai.io/api/v1/billing/freshness" \
-H "Authorization: Bearer sk-syn-bill-..." Esempio di risposta
{
"data": {
"data_complete_until": "2026-09-24T04:00:00Z",
"latest_recorded_at": "2026-09-24T05:58:31Z",
"safe_until": "2026-09-24T05:58:31Z",
"pipeline_lag_seconds": 47
},
"meta": { "generated_at": "2026-09-24T06:00:02Z" }
} GET /balance - quanto il workspace può spendere ora
GET /balance
Restituisce il saldo disponibile attuale del workspace, lo stesso importo mostrato in console. Pensato per interrogazioni periodiche: ogni 30-60 secondi è più che sufficiente. Ha un proprio limite di frequenza, quindi interrogarlo non consuma il budget degli endpoint dei dati, e risponde anche quando il saldo è zero.
Esempio di richiesta
curl "https://synthorai.io/api/v1/billing/balance" \
-H "Authorization: Bearer sk-syn-bill-..." Esempio di risposta
{
"data": {
"available_usd": 1284.37,
"debt_usd": 0,
"source": "live",
"voucher": null,
"scheduled_credits": [
{ "amount_usd": 250, "release_at": "2026-10-01T00:00:00Z" }
],
"scheduled_credit_total_usd": 250
},
"meta": { "generated_at": "2026-09-24T06:00:02Z", "currency": "USD" }
} | Parametro | Descrizione |
|---|---|
available_usd | Il saldo generale su cui vengono addebitate le richieste in questo momento. Mai sotto 0. Può risalire brevemente quando le richieste in corso vengono regolate; per la spesa usa /usage o /records. |
debt_usd | Il saldo dovuto: il consumo oltre l'importo accreditato. 0 se non c'è nulla da pagare. Una ricarica lo salda per primo; solo il resto va in available_usd. |
source | live: letto dal registro in tempo reale. delayed: il registro non era raggiungibile e il valore proviene da una copia nel database che può essere indietro di circa 30 secondi. |
voucher | Credito promozionale solo per la generazione di immagini (applies_to), sui modelli elencati e fino a expires_at; le altre richieste usano solo available_usd, e questo credito non ne fa parte. null se assente; active è false dopo la scadenza. |
scheduled_credits | Crediti già concordati che arrivano a rate (amount_usd, release_at); ogni rata viene aggiunta al saldo entro circa 10 minuti da release_at, non prima. scheduled_credit_total_usd ne è la somma. |
Errori
Ogni errore è {"error": {"type", "message", "hint"}}:
{
"error": {
"type": "range_too_large",
"message": "the requested range exceeds this endpoint's cap",
"hint": "narrow start/end to at most 31 days, or sync /records with a cursor instead"
}
} | Tipo | Stato HTTP | Significato |
|---|---|---|
invalid_parameter | 400 | Un parametro di query è mancante, malformato o fuori intervallo. |
range_too_large | 400 | L'intervallo di tempo o la finestra di id richiesti sono più grandi di quanto consentito dall'endpoint; il hint indica il limite in questione. |
start_too_recent | 400 | Una finestra start/end inizia dopo safe_until, dove il suo limite iniziale non è ancora stabile. Riprova dopo il tempo indicato dall'header Retry-After, oppure sincronizza con un cursor. |
wrong_key_kind | 401 | La credenziale non è una chiave di fatturazione, oppure una chiave di fatturazione è stata usata fuori da questi endpoint. |
authentication_error | 401 | La chiave è assente, non valida, scaduta, oppure bloccata dalla sua whitelist di IP. |
key_owner_not_authorized | 403 | Il creatore di questa chiave non è più il proprietario del workspace. Le chiavi di fatturazione sono esclusive del proprietario e smettono di funzionare nel momento in cui la proprietà cambia; il nuovo proprietario deve crearne una propria. |
rate_limited | 429 | Più di 60 richieste al minuto su questo workspace. Riprova dopo il tempo indicato nell'header Retry-After. |
too_many_concurrent_requests | 429 | Più di 3 richieste contemporanee su questo workspace, oppure un'altra esportazione già in corso. |
replica_unavailable | 503 | Questo processo non ha alcuna replica di lettura configurata; l'API non ricorre mai alla primaria. |
query_timeout | 503 | La query ha superato il timeout di 10 secondi o il termine di 15 secondi. Restringi l'intervallo e riprova. |
internal_error | 500 | Un errore imprevisto del server. Riprova e segnalalo se persiste. |
Limiti di frequenza e massimali
| Protezione | Valore |
|---|---|
| Limite di frequenza | 60 richieste al minuto per workspace, una finestra scorrevole condivisa tra tutte le istanze. |
| Concorrenza | 3 richieste contemporanee per workspace su ogni server, e uno stream di esportazione per workspace su ogni server. |
| Interrogazione del saldo | /balance: 60 richieste al minuto e 2 in parallelo per workspace, conteggiate separatamente dagli altri endpoint. |
| Massimali di intervallo | /usage: 31 giorni con granularità hour, 366 giorni con day. /summary: fino a 366 giorni. /records e l'esportazione: fino a 31 giorni. |
| Massimali di pagina | /usage restituisce fino a 5.000 righe per pagina, /records fino a 1.000. L'esportazione trasmette pagine di 2.000 e si arresta a 1.000.000 di righe - riprendibile con cursor. Si ferma anche dopo aver percorso 2.000.000 di id di record (stopped_because=scan_cap) e regola il ritmo sul database; riprendi con cursor. |
| Caching | Ogni risposta porta Cache-Control: no-store. |
Non viene mai restituito: channel_id, upstream_request_id, usage_raw, other, content, ip_address, user_agent, cost_detail.
Evitare i vuoti temporali
Le richieste vengono scritte nel log in modo asincrono dopo il loro completamento (di norma pochi secondi, più a lungo in caso di accumulo), e il rollup orario letto da /usage e /summary viene caricato in batch. Le ore più recenti sono sempre un po' incomplete. /usage e /summary aggiungono anche le richieste scritte dall'ultima ingestione (meta.live_tail è true); se l'aggregato è troppo indietro, meta.live_tail è false e i bucket recenti sono parziali.
data_complete_until è il più antico tra questi due punti: il livello di riferimento del rollup orario meno il suo ritardo di pipeline attuale, oppure la richiesta più vecchia non ancora inclusa nel rollup - meno un margine di sicurezza di 30 minuti, arrotondato all'ora inferiore.
Un bucket finale è stabile, con un'eccezione: il job di riconciliazione giornaliera può ancora correggere un'ora entro 48 ore se rileva una deriva. /records è sempre la fonte di verità per i numeri esatti di una richiesta.
Ogni riga di /usage porta un indicatore final : true , valido non appena il suo bucket termina prima o entro data_complete_until (valore di /freshness). Un bucket finale non cambia mai più.
- Dati aggregati: esegui l'upsert di ogni riga nel tuo archivio in base a (bucket_start, api_key_id, model), e recupera di nuovo qualsiasi bucket mentre è ancora final: false. Non calcolare mai un delta sottraendo un totale vecchio da uno nuovo.
- Righe grezze: sincronizza per cursor, mai per tempo. Conserva next_cursor e riprendi da lì alla chiamata successiva, e deduplica le righe ricevute in base a record_id (un id opaco stabile per riga) nel caso in cui una pagina ripetuta riconsegni la stessa riga. Le righe più recenti di circa 5 minuti vengono trattenute - safe_until è l'orario della riga registrata più di recente meno 5 minuti - così una riga ancora in fase di commit non può mai scivolare prima di una posizione di cursore già consegnata, e niente cade in un vuoto. Per contare le richieste, conta i request_id distinti.
- Finestra limitata: se interroghi un intervallo start/end invece di sincronizzare per cursor, considéralo completo solo quando window_final è true (è true solo se hai fornito un end ed è precedente o uguale a safe_until). Se window_final è false, richiama più tardi con lo stesso intervallo, oppure passa alla sincronizzazione per cursor. Una finestra deve iniziare al più tardi a safe_until; un inizio successivo restituisce 400 start_too_recent con un header Retry-After.
Integrazione consigliata
- Fatturazione notturna: chiama /usage una volta al giorno con granularity=day, e recupera di nuovo qualsiasi bucket che la tua ultima esecuzione vedeva ancora come final: false.
- Dettaglio per richiesta: chiama /records ogni ora con cursor impostato sull'ultimo next_cursor salvato, continua a paginare finché has_more è true, e deduplica le righe ricevute in base a record_id.
- Dashboard: chiama /summary per l'intervallo visibile invece di aggregare /records da solo - porta già l'elenco delle anomalie.
Devi generare o gestire chiavi API normali in modo programmatico invece di leggere dati di fatturazione? Vedi Chiavi di provisioning.