Nouveau Inscription gratuite, 10 appels offerts. Jusqu'à 1 $, sans carte.

API de facturation

Récupérez l'usage et le coût de votre propre workspace dans vos propres systèmes, sans ouvrir la console. Une clé de facturation est un en lecture seule identifiant limité à un seul workspace - elle peut lire les données de facturation, rien de plus.

⚠

Une clé de facturation peut lire chaque requête enregistrée dans son workspace, y compris les clés depuis supprimées ou révoquées, mais elle ne peut appeler aucun modèle, ni créer, modifier ou supprimer de clé API. L'utiliser ailleurs renvoie 401 wrong_key_kind.

Créer une clé de facturation

  1. Ouvrez Console → Clés d'API de facturation (propriétaire du workspace uniquement). Nommez-la, restreignez-la éventuellement à une liste blanche d'IP ou définissez une expiration, puis copiez la clé - elle commence par sk-syn-bill- et ne s'affiche qu'une seule fois.
  2. Appelez les endpoints ci-dessous en l'utilisant comme jeton Bearer. Révoquez-la à tout moment depuis la même page - la révocation l'invalide immédiatement et n'affecte rien d'autre dans le workspace.
  3. Une clé est liée à son créateur : s'il n'est plus le propriétaire du workspace, elle cesse de fonctionner et renvoie 403 key_owner_not_authorized.

Authentification

Chaque appel nécessite Authorization: Bearer sk-syn-bill-... envoyé à l'URL de base ci-dessous. Une clé de facturation ne fonctionne que sur ces endpoints, et ces endpoints n'acceptent qu'une clé de facturation - toute autre combinaison renvoie 401 wrong_key_kind.

Authorization: Bearer sk-syn-bill-...

URL de base: https://synthorai.io/api/v1/billing

ℹ

La portée couvre tout le workspace : chaque clé d'inférence qu'il contient, y compris celles supprimées - leurs lignes historiques restent interrogeables. api_key_id et model ne peuvent que restreindre le résultat, jamais l'élargir ; chaque requête est ancrée sur le workspace propre de la clé.

ℹ

Chaque requête s'exécute sur la réplique en lecture, jamais sur la primaire. Un processus sans réplique configurée répond 503 replica_unavailable plutôt que de basculer silencieusement.

✓

Champs de tokens : suivent la convention OpenAI - prompt_tokens est le total des tokens d'entrée et inclut déjà chaque lecture et écriture de cache ci-dessous, et completion_tokens inclut déjà reasoning_tokens. Aucun des deux ne s'ajoute aux totaux - ce sont des décompositions, pas des tokens supplémentaires.

La part de lecture de cache de prompt_tokens est cached_tokens ; la part d'écriture de cache est cache_write_tokens, elle-même divisée sur les lignes de /records en cache_write_5m_tokens et cache_write_1h_tokens selon le TTL.

✓

Champs monétaires : cost_usd est le montant réellement facturé. byok_list_price_usd est ce qu'aurait coûté une requête BYOK au prix catalogue - 0 pour une requête non BYOK. Il n'existe pas de list_price_usd nulle part dans cette API.

ℹ

Ici, "erreur" désigne toute requête qui a atteint l'API et a été rejetée - un 4xx comme 402 (solde insuffisant), 403 ou 429, ainsi qu'un échec en amont. Seuls nos propres rejets d'infrastructure interne sont exclus, exactement comme dans la console. Les rejets sont enregistrés au plus une fois par clé, code de statut et minute sur chaque serveur : les nombres de 402, 403 et 429 sont donc des minimums ; les requêtes réussies et les montants facturés sont complets.

GET /usage - usage agrégé

GET /usage

Des lignes pré-agrégées par heure ou par jour - pour la facturation et les tableaux de bord. Elles viennent de l'agrégat horaire, complété des requêtes écrites depuis sa dernière ingestion, si bien que les buckets récents n'ont pas un cycle d'ingestion entier de retard.

ParamètreTypeDescription
start*stringDébut de la plage : un horodatage RFC 3339 ou une date telle que 2026-09-01.
endstringFin de la plage. Par défaut, l'heure actuelle.
granularitystringTaille du bucket : hour ou day. Par défaut, day.
group_bystringDimensions selon lesquelles répartir les lignes : api_key, model, ou les deux (tout sous-ensemble). Par défaut, les deux.
api_key_idintegerFiltre sur un ou plusieurs id de clé API. Répétable ; ne fait que restreindre, jamais élargir.
modelstringFiltre sur un ou plusieurs id de modèle. Répétable.
limitintegerLignes par page. Par défaut 1000, plafonné à 5000.
cursorstringCurseur de pagination opaque, repris du meta.next_cursor de la page précédente.

Dans chaque ligne, api_key_id et api_key_name n'apparaissent que lorsque group_by inclut api_key, et model uniquement lorsqu'il inclut model. avg_latency_ms peut être null quand un bucket n'a aucun échantillon de latence.

Exemple de requête

curl "https://synthorai.io/api/v1/billing/usage?start=2026-09-01&end=2026-09-24&granularity=day" \
  -H "Authorization: Bearer sk-syn-bill-..."

Exemple de réponse

{
  "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 - lignes par requête

GET /records

Une ligne par requête, destinée à une synchronisation incrémentale vers votre propre base de données. Synchronisez par cursor, jamais par le temps, et dédupliquez les lignes reçues par record_id - voir "Éviter les trous temporels" ci-dessous pour la raison.

ParamètreTypeDescription
cursorstringCurseur de synchronisation opaque (chaîne), repris du meta.next_cursor de l'appel précédent. Fournissez exactement l'un des deux, cursor ou start ; il n'a aucun plafond de plage.
startstringDébut de la plage (horodatage RFC 3339 ou date). Fournissez exactement l'un des deux, cursor ou start ; une plage start/end est plafonnée à 31 jours, tandis que cursor n'a aucun plafond de plage.
endstringFin de la plage. Par défaut, l'heure actuelle.
api_key_idintegerFiltre sur un ou plusieurs id de clé API. Répétable ; ne fait que restreindre, jamais élargir.
modelstringFiltre sur un ou plusieurs id de modèle. Répétable.
limitintegerLignes par page. Par défaut 500, plafonné à 1000.

Exemple de requête

curl "https://synthorai.io/api/v1/billing/records?start=2026-09-24T00:00:00Z&limit=500" \
  -H "Authorization: Bearer sk-syn-bill-..."

Exemple de réponse

{
  "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"
  }
}
ParamètreDescription
next_cursorCurseur pour le prochain appel. Toujours présent.
has_moreIndique si un nouvel appel immédiat avec ce curseur renverrait davantage de lignes.
window_finaltrue uniquement si vous avez fourni un end et qu'il est à ou avant safe_until - la plage demandée est alors garantie complète. Absent ou false sinon.
safe_untilLe recorded_at le plus récent qu'un appelant peut considérer comme définitif ; les lignes plus récentes que cela sont encore retenues.
latest_recorded_atLe recorded_at le plus récent parmi toutes les lignes, définitives ou non.
data_complete_untilMême valeur que celle renvoyée par /freshness - jusqu'où le rollup horaire est complet, pour recouper avec /usage.
⚠

Base temporelle : /usage regroupe par completed_at (quand la requête s'est terminée). Le filtre start/end de /records se base sur recorded_at (quand la ligne a été écrite, ce qui peut retarder par rapport à l'achèvement sous forte charge). Pour recouper /records avec /usage, regroupez vous-même les lignes synchronisées par completed_at, pas par recorded_at.

GET /records/export - export en flux

GET /records/export

Mêmes filtres que /records, diffusés en JSON délimité par des retours à la ligne (application/x-ndjson) - conçu pour un grand rattrapage plutôt qu'une liste paginée. Un seul export peut être diffusé par workspace à la fois ; un second reçoit 429 too_many_concurrent_requests.

Exemple de requête

curl -N "https://synthorai.io/api/v1/billing/records/export?cursor=eyJvIjoiODgxNDAzMiJ9" \
  -H "Authorization: Bearer sk-syn-bill-..."

Exemple de réponse

{"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"}}

La dernière ligne du flux est un _meta objet :

ParamètreDescription
next_cursorCurseur pour le prochain appel d'export.
rowsNombre d'enregistrements écrits dans ce flux.
completefalse signifie que le flux s'est arrêté avant d'épuiser la plage - reprenez avec cursor=next_cursor.
window_finalMême signification que sur /records : true uniquement si le end de l'export est à ou avant safe_until.
generated_atMoment où cette ligne a été écrite.
stopped_becausePrésent quand complete vaut false : row_cap, scan_cap (le flux a parcouru sa plage d'id maximale sans se remplir - fréquent avec un filtre étroit), time_cap, query_error ou window_not_final.

GET /summary - statistiques préliminaires et anomalies

GET /summary

Totaux, meilleurs modèles et meilleures clés par coût, et un taux d'erreur pour la plage, plus une anomalies liste (high_error_rate, rate_limited, data_not_final, rollup_delayed), chacune portant une gravité info ou warning et un message lisible. Les chiffres d'une plage non finalisée peuvent encore changer - voir plus bas.

ParamètreTypeDescription
startstringDébut de la plage. Par défaut, 30 jours avant end.
endstringFin de la plage. Par défaut, l'heure actuelle.
api_key_idintegerFiltre sur un ou plusieurs id de clé API. Répétable ; ne fait que restreindre, jamais élargir.
modelstringFiltre sur un ou plusieurs id de modèle. Répétable.

models_count et keys_count donnent les totaux derrière top_models et top_keys, qui ne listent que les 20 premiers par coût au maximum. La plage est par défaut les 30 jours avant end lorsque start est omis.

Exemple de requête

curl "https://synthorai.io/api/v1/billing/summary?start=2026-09-17&end=2026-09-24" \
  -H "Authorization: Bearer sk-syn-bill-..."

Exemple de réponse

{
  "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 - degré de complétude des données

GET /freshness

Renvoie data_complete_until, latest_recorded_at, safe_until et pipeline_lag_seconds - interrogez-le avant de considérer une plage tout juste récupérée comme définitive. safe_until est le recorded_at le plus récent qu'un appelant peut considérer comme complet ; des lignes plus récentes peuvent encore arriver.

Exemple de requête

curl "https://synthorai.io/api/v1/billing/freshness" \
  -H "Authorization: Bearer sk-syn-bill-..."

Exemple de réponse

{
  "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 - ce que l'espace de travail peut dépenser maintenant

GET /balance

Renvoie le solde disponible actuel de l'espace de travail - le même montant que dans la console. Conçu pour l'interrogation périodique : toutes les 30 à 60 secondes suffisent. Il a sa propre limite de débit, donc l'interroger n'entame pas le budget des endpoints de données, et il répond même quand le solde est à zéro.

Exemple de requête

curl "https://synthorai.io/api/v1/billing/balance" \
  -H "Authorization: Bearer sk-syn-bill-..."

Exemple de réponse

{
  "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" }
}
ParamètreDescription
available_usdLe solde général sur lequel les requêtes sont facturées en ce moment. Jamais en dessous de 0. Il peut remonter brièvement quand des requêtes en cours sont réglées ; pour les dépenses, utilisez /usage ou /records.
debt_usdLe solde dû : la consommation au-delà du montant crédité. 0 s'il n'y a rien à payer. Une recharge le règle en premier ; seul le reste s'ajoute à available_usd.
sourcelive : lu dans le registre en temps réel. delayed : le registre était injoignable, et la valeur vient d'une copie en base qui peut avoir environ 30 secondes de retard.
voucherCrédit promotionnel réservé à la génération d'images (applies_to), sur les modèles listés et jusqu'à expires_at ; les autres requêtes n'utilisent que available_usd, et ce crédit n'en fait pas partie. null s'il n'y en a pas ; active vaut false une fois expiré.
scheduled_creditsCrédits déjà convenus versés par tranches (amount_usd, release_at) ; chaque tranche est ajoutée au solde dans les 10 minutes environ suivant release_at, pas avant. scheduled_credit_total_usd en est la somme.

Erreurs

Chaque erreur est {"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"
  }
}
TypeStatut HTTPSignification
invalid_parameter400Un paramètre de requête est manquant, malformé ou hors plage.
range_too_large400La plage temporelle ou la fenêtre d'id demandée dépasse ce que l'endpoint autorise ; le hint indique le plafond concerné.
start_too_recent400Une fenêtre start/end commence après safe_until, là où sa borne de début n'est pas encore stabilisée. Réessayez après le délai de l'en-tête Retry-After, ou synchronisez avec un cursor.
wrong_key_kind401L'identifiant n'est pas une clé de facturation, ou une clé de facturation a été utilisée hors de ces endpoints.
authentication_error401La clé est manquante, invalide, expirée, ou bloquée par sa liste blanche d'IP.
key_owner_not_authorized403Le créateur de cette clé n'est plus le propriétaire du workspace. Les clés de facturation sont réservées au propriétaire et cessent de fonctionner dès que la propriété change de main ; le nouveau propriétaire doit créer la sienne.
rate_limited429Plus de 60 requêtes par minute sur ce workspace. Réessayez après le délai indiqué dans l'en-tête Retry-After.
too_many_concurrent_requests429Plus de 3 requêtes simultanées sur cet espace de travail, ou un autre export déjà en cours.
replica_unavailable503Ce processus n'a aucune réplique en lecture configurée ; l'API ne bascule jamais vers la primaire.
query_timeout503La requête a dépassé le délai d'expiration de 10 secondes ou l'échéance de 15 secondes. Restreignez la plage et réessayez.
internal_error500Une erreur serveur inattendue. Réessayez, et signalez-la si elle persiste.

Limites de débit et plafonds

Garde-fouValeur
Limite de débit60 requêtes par minute par workspace, une fenêtre glissante partagée entre toutes les instances.
Concurrence3 requêtes simultanées par espace de travail sur chaque serveur, et un flux d'export par espace de travail sur chaque serveur.
Interrogation du solde/balance : 60 requêtes par minute et 2 en parallèle par espace de travail, comptées à part des autres endpoints.
Plafonds de plage/usage : 31 jours en granularité hour, 366 jours en day. /summary : jusqu'à 366 jours. /records et l'export : jusqu'à 31 jours.
Plafonds de pagination/usage renvoie jusqu'à 5 000 lignes par page, /records jusqu'à 1 000. L'export diffuse des pages de 2 000 et s'arrête à 1 000 000 de lignes - reprenable avec cursor. Il s'arrête aussi après avoir parcouru 2 000 000 d'id d'enregistrement (stopped_because=scan_cap) et règle son rythme sur la base de données ; reprenez avec cursor.
Mise en cacheChaque réponse porte Cache-Control: no-store.

Jamais renvoyé : channel_id, upstream_request_id, usage_raw, other, content, ip_address, user_agent, cost_detail.

Éviter les trous temporels

Les requêtes sont écrites dans le log de façon asynchrone après leur fin (quelques secondes normalement, plus en cas de retard accumulé), et le rollup horaire lu par /usage et /summary est ingéré par lots. Les heures les plus récentes sont toujours un peu incomplètes. /usage et /summary ajoutent aussi les requêtes écrites depuis la dernière ingestion (meta.live_tail vaut true) ; si l'agrégat a trop de retard pour cela, meta.live_tail vaut false et les buckets récents sont partiels.

data_complete_until est le plus tôt des deux points suivants : le filigrane du rollup horaire moins son retard de pipeline actuel, ou la requête la plus ancienne pas encore intégrée au rollup - moins une marge de sécurité de 30 minutes, arrondi à l'heure inférieure.

ℹ

Un bucket final est stable, à une exception près : la tâche de réconciliation quotidienne peut encore corriger une heure dans les 48 heures si elle détecte une dérive. /records reste toujours la source de vérité pour les chiffres exacts d'une requête.

Chaque ligne de /usage porte un indicateur final : true , vrai dès que son bucket se termine avant ou à data_complete_until (valeur renvoyée par /freshness). Un bucket final ne change plus jamais.

  • Agrégats: upsertez chaque ligne dans votre propre stockage selon (bucket_start, api_key_id, model), et récupérez à nouveau tout bucket tant qu'il est final: false. Ne calculez jamais un delta en soustrayant un ancien total d'un nouveau.
  • Lignes brutes: synchronisez par cursor, jamais par le temps. Conservez next_cursor et reprenez à partir de là au prochain appel, et dédupliquez les lignes reçues par record_id (un id opaque stable par ligne) au cas où une page reprise redélivrerait la même ligne. Les lignes de moins d'environ 5 minutes sont retenues - safe_until est l'heure de la ligne la plus récemment enregistrée moins 5 minutes - si bien qu'une ligne encore en cours d'écriture ne peut jamais se glisser avant une position de curseur déjà distribuée, et rien ne tombe dans un trou. Pour compter les requêtes, comptez les request_id distincts.
  • Plage bornée: si vous interrogez une plage start/end au lieu de synchroniser par cursor, ne la considérez complète que lorsque window_final est true (il n'est true que si vous avez fourni un end et qu'il est à ou avant safe_until). Si window_final est false, rappelez plus tard avec la même plage, ou passez à une synchronisation par cursor. Une fenêtre doit commencer au plus tard à safe_until ; un début plus tardif renvoie 400 start_too_recent avec un en-tête Retry-After.

Intégration recommandée

  • Facturation nocturne: appelez /usage une fois par jour avec granularity=day, et récupérez à nouveau tout bucket que votre dernière exécution voyait encore comme final: false.
  • Détail par requête: appelez /records chaque heure avec cursor réglé sur le dernier next_cursor que vous avez stocké, continuez à paginer tant que has_more est true, et dédupliquez les lignes reçues par record_id.
  • Tableaux de bord: appelez /summary pour la plage visible plutôt que d'agréger /records vous-même - elle porte déjà la liste des anomalies.

Besoin de générer ou de gérer des clés API classiques par programmation plutôt que de lire des données de facturation ? Voir Clés de provisioning.