> ## Documentation Index
> Fetch the complete documentation index at: https://docs.actero.fr/llms.txt
> Use this file to discover all available pages before exploring further.

# GET /billing/usage

> Récupère la consommation de tickets du mois en cours, le plan actif, le solde de crédits et l'état du quota.

L'endpoint `billing/usage` te retourne la consommation du mois calendaire courant pour un tenant donné. C'est ce que le dashboard utilise pour afficher la jauge "X / Y tickets utilisés" et estimer la prochaine facture.

```http theme={null}
GET https://actero.fr/api/billing/usage?client_id=<id>
```

## Authentification

`Authorization: Bearer <jwt>` ou `Bearer <ak_…>`.

L'utilisateur doit être :

* **Admin Actero** (équipe interne, accès à tous les tenants), **ou**
* **Membre du `client_id`** (présent dans la table `client_users`).

Sinon → `403 Acces refuse.`

## Query params

<ParamField query="client_id" type="string" required>
  ULID du tenant. Si absent → `400 Missing client_id`.
</ParamField>

## Headers

| Header          | Valeur           | Requis |
| --------------- | ---------------- | ------ |
| `Authorization` | `Bearer <token>` | Oui    |

## Response — `200 OK`

```json theme={null}
{
  "plan": "pro",
  "period": "2026-04",
  "tickets_used": 1247,
  "tickets_limit": 5000,
  "voice_minutes_used": 38,
  "voice_minutes_limit": 0,
  "overage_tickets": 0,
  "overage_cost": 0,
  "trial_ends_at": null,
  "next_billing_date": "2026-05-01"
}
```

<ResponseField name="plan" type="enum">`free`, `starter`, `pro`, `enterprise`. Lu depuis `clients.plan`.</ResponseField>

<ResponseField name="period" type="string">Mois calendaire au format `YYYY-MM`. Tous les compteurs ci-dessous sont **strictement** sur ce mois — ils repassent à 0 le 1er du mois suivant.</ResponseField>

<ResponseField name="tickets_used" type="integer">Nombre de tickets traités par l'agent ce mois (passage Brain + Executor, peu importe le résultat).</ResponseField>

<ResponseField name="tickets_limit" type="integer">Quota du plan. Vaut **`-1`** si illimité (Enterprise).</ResponseField>

<ResponseField name="voice_minutes_used" type="number">Minutes consommées par l'agent vocal. Toujours `0` — l'agent vocal n'est pas encore disponible.</ResponseField>

<ResponseField name="voice_minutes_limit" type="number">Quota minutes vocales du plan. Toujours `0` tant que l'agent vocal n'est pas lancé.</ResponseField>

<ResponseField name="overage_tickets" type="integer">Déprécié — toujours `0`. Aucun plan ne facture de dépassement (plafond strict).</ResponseField>

<ResponseField name="overage_cost" type="number">Déprécié — toujours `0`.</ResponseField>

<ResponseField name="quota_reached" type="boolean">`true` quand le quota mensuel est atteint : l'agent est en pause jusqu'à l'achat de crédits ou un upgrade.</ResponseField>

<ResponseField name="credits_balance" type="integer">Crédits disponibles. Consommés automatiquement (1 crédit = 1 ticket) au-delà du quota.</ResponseField>

<ResponseField name="trial_ends_at" type="string|null">ISO timestamp de fin de période d'essai. `null` si le tenant n'est pas en trial.</ResponseField>

<ResponseField name="next_billing_date" type="string">Date approximative de prochain prélèvement Stripe (1er du mois suivant). Format `YYYY-MM-DD`.</ResponseField>

## Response — autres cas

### `400 Bad Request`

```json theme={null}
{ "error": "Missing client_id" }
```

### `401 Unauthorized`

```json theme={null}
{ "error": "Non autorise." }
```

### `403 Forbidden`

```json theme={null}
{ "error": "Acces refuse." }
```

### `404 Not Found`

```json theme={null}
{ "error": "Client introuvable." }
```

### `405 Method Not Allowed`

Cet endpoint accepte uniquement `GET`. Tout autre verbe renvoie `405`.

### `500 Internal Server Error`

```json theme={null}
{ "error": "Erreur interne." }
```

## Dépassement de quota

| Plan       | Tickets inclus | Au-delà du quota         |
| ---------- | -------------- | ------------------------ |
| Free       | 50             | Bloqué (crédits/upgrade) |
| Starter    | 1 000          | Bloqué (crédits/upgrade) |
| Pro        | 5 000          | Bloqué (crédits/upgrade) |
| Enterprise | Illimité       | —                        |

<Note>
  Les `price_ids` Stripe ne sont **pas** exposés via l'API publique — ils restent server-side. Aucun dépassement n'est facturé : surveille `quota_reached` et `credits_balance` pour savoir si l'agent est en pause.
</Note>

## Exemples

<CodeGroup>
  ```bash curl theme={null}
  curl "https://actero.fr/api/billing/usage?client_id=$ACTERO_CLIENT_ID" \
    -H "Authorization: Bearer $ACTERO_API_KEY"
  ```

  ```js Node.js theme={null}
  const url = new URL('https://actero.fr/api/billing/usage')
  url.searchParams.set('client_id', process.env.ACTERO_CLIENT_ID)

  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.ACTERO_API_KEY}` },
  })
  const usage = await res.json()

  const pct = (usage.tickets_used / usage.tickets_limit) * 100
  console.log(`${usage.tickets_used}/${usage.tickets_limit} tickets (${pct.toFixed(1)}%)`)

  if (usage.quota_reached) {
    console.warn(`Quota atteint — agent en pause. Crédits restants : ${usage.credits_balance}`)
  }
  ```

  ```python Python theme={null}
  import os, requests

  res = requests.get(
      "https://actero.fr/api/billing/usage",
      params={"client_id": os.environ["ACTERO_CLIENT_ID"]},
      headers={"Authorization": f"Bearer {os.environ['ACTERO_API_KEY']}"},
  )
  usage = res.json()

  if usage["tickets_limit"] != -1:
      pct = usage["tickets_used"] / usage["tickets_limit"] * 100
      print(f"{usage['tickets_used']}/{usage['tickets_limit']} tickets ({pct:.1f}%)")
  ```
</CodeGroup>

## Polling vs webhook

Pour suivre la consommation en temps réel **sans** poller cet endpoint en continu, abonne-toi à l'événement `usage.threshold_reached` via un [webhook sortant](/api-reference/webhooks). Tu reçois un POST quand le tenant franchit 80 % puis 100 % de son quota mensuel — utile pour :

* Notifier l'équipe finance avant le hard cap (Free).
* Déclencher un upgrade automatique côté ton CRM.
* Pause-r ton crawl/import si tu sais que chaque event = 1 ticket.

## Rate limit

60 requêtes / minute / user. Largement suffisant pour un polling raisonnable (1× par heure suffit dans 99 % des cas).
