> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://developer.jelliu.co/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://developer.jelliu.co/_mcp/server.

# Retrieve usage for the current month

GET https://api.jelliu.co/api/billing/usage

Voice-minute and AI-message consumption for the current **monthly usage window**. On
monthly plans and trials the window is the billing period. On annual plans it is the
one-month slice of the yearly period that contains today, so the allowance resets every
month.

How to read it:
- Available minutes = `includedMinutes` + `bonusMinutes`. While trialing, `includedMinutes` is the 15-minute trial allowance.
- `overageMinutes` = minutes used beyond the available minutes. `overageCostCents` = those minutes × $0.45 (Enterprise $0.33), in US cents. Both are always 0 while trialing.
- Calls are refused once use passes 150% of the included minutes plus bonus minutes. Pending overage is charged immediately when it reaches $25.
- `chatMessagesUsed` counts AI replies on WhatsApp, email, Instagram, Messenger and webchat. The allowance is a hard cap with no overage: AI replies stop at the cap until the window resets.

A workspace with no subscription gets zeros (with `channels: ["voice"]`). To buy
headroom, see `POST /api/billing/minute-packs` or `POST /api/billing/change-plan`.

**Consistency.** Cached server-side for 30 seconds. Minutes are recorded when a call
ends, so a call in progress is not counted yet.

**Access**
- **Required scope:** `full`. Signed-in users need the `owner`, `admin` or `billing` role.
- **Rate limit:** Billing — 15 requests/min per workspace, shared by every `/api/billing` route. See [Rate limits](/rate-limits).
- **Plan:** Available on every plan.


Reference: https://developer.jelliu.co/api-reference/billing/get-billing-usage

## Authentication

- `Authorization` header (bearer token, required) — Workspace API key: `jl_` followed by 64 lowercase hex characters, created by the workspace owner in the dashboard (**Settings → API Keys**) and sent as `Authorization: Bearer jl_...`. The plaintext is shown once, at creation; Jelliu stores only a SHA-256 hash. A workspace can hold up to 25 active keys. | Scope | GET / HEAD | POST / PUT / PATCH / DELETE | Admin-only routes | | --- | --- | --- | --- | | `read` | Yes | No | No | | `write` | Yes | Yes | No | | `full` | Yes | Yes | Yes | Operations restricted to admins or owners reject keys without the `full` scope with `403`, and say so in their description. No key, whatever its scope, can mint or revoke API keys or rotate a webhook secret — that requires a signed-in owner session. A revoked key stops authenticating within about 10 seconds. See [Authentication](/authentication).

## Response

### 200

Usage for the current monthly window.

- `data` (object, required)
  - `minutesUsed` (double, optional) — Voice minutes consumed in the current monthly window.
  - `includedMinutes` (integer, optional) — Monthly plan allowance, or the 15-minute trial allowance while trialing.
  - `bonusMinutes` (double, optional) — Minutes still left in purchased minute packs. Packs are drawn down (oldest first) only by minutes beyond the included allowance. They do not expire and are not reset monthly.
  - `overageMinutes` (double, optional) — `minutesUsed` minus (`includedMinutes` + `bonusMinutes`), floored at 0. Always 0 while trialing. Because `bonusMinutes` is what is LEFT in the packs, once pack minutes have been drawn down this figure also counts the minutes the packs covered, so it can read higher than the overage actually invoiced (which excludes them).
  - `overageCostCents` (double, optional) — `overageMinutes` × the per-minute overage price, in US cents (4500 = $45.00). Always 0 while trialing.
  - `usagePercentage` (double, optional) — `minutesUsed` as a percentage of `includedMinutes` + `bonusMinutes`, rounded to two decimals. Can exceed 100 when in overage.
  - `isTrialing` (boolean, optional) — True while the subscription is in its trial.
  - `trialMinutesLeft` (double, optional) — Trial minutes remaining (15 minus used, never negative). 0 when not trialing.
  - `alertTriggered` (boolean, optional) — True at 80% or more of the available minutes.
  - `chatMessagesUsed` (integer, optional) — AI replies sent in the current window on the metered text channels (WhatsApp, email, Instagram, Messenger, webchat).
  - `includedChatMessages` (integer, optional) — Monthly AI-reply allowance. -1 means unlimited (Enterprise); 0 with no subscription.
  - `chatUsagePercentage` (double, optional) — `chatMessagesUsed` as a percentage of `includedChatMessages`, two decimals. 0 when the allowance is unlimited.
  - `channels` (list of enum, optional) — Channels the plan includes.
    - Allowed values: `voice`, `whatsapp`, `webchat`, `email`
  - `excludedMetrics` (list of string, optional) — Metrics that do not apply because the plan has no voice channel. Empty on every current plan.

## Errors

### 401 Unauthorized Error

No usable credential. Either the `Authorization` header is missing or is not a well-formed `Bearer jl_…` key, or the key is unknown, revoked or expired. Do not retry with the same key. See [Authentication](/authentication#401-unauthorized).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 403 Forbidden Error

The API key lacks the `full` scope, the user's role is not `owner`, `admin` or `billing`, or the workspace is suspended.

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

### 429 Too Many Requests Error

Billing rate limit exceeded (15 requests per minute per workspace).

- `error` (object, required) — The error object. Always has `code` and `message`.
  - `code` (string, required) — Stable machine-readable error code (for example `VALIDATION_FAILED`, `UNAUTHORIZED`, `FORBIDDEN`, `NOT_FOUND`, `BILLING_ERROR`, `COMPLIANCE_BLOCKED`, `RATE_LIMIT_EXCEEDED`, `INTERNAL_ERROR`). Switch on this, not on `message`. See [Errors](/errors).
  - `message` (string, required) — Human-readable explanation. English or Spanish depending on the route; may change without notice.
  - `details` (object or list of object, optional) — Present on `VALIDATION_FAILED` only. Its shape depends on how the route validates: * a field map, either Zod's `flatten()` output (`{ "formErrors": [], "fieldErrors": { "name": ["..."] } }`) or just its `fieldErrors` part (`{ "name": ["..."] }`); * an issue list, where each issue has at least `path`, `message` and `code`. `path` is a dot-separated string on routes that let the schema throw, and an array of keys on routes that forward Zod's raw issues (those also carry Zod's extra issue fields).
    - Field map
      - `formErrors` (list of string, optional)
      - `fieldErrors` (map from string to list of string, optional)
  - `metadata` (map from string to any, optional) — Structured detail exposed for a small allowlist of codes only — for example `BILLING_ERROR` carries `limit`, `current` and `tier` (resource caps) or `tier` and `feature` (feature gates).

## Examples

### Growth plan drawing on a 500-minute pack

**Response**

```json
{
  "data": {
    "minutesUsed": 702,
    "includedMinutes": 620,
    "bonusMinutes": 418,
    "overageMinutes": 0,
    "overageCostCents": 0,
    "usagePercentage": 67.63,
    "isTrialing": false,
    "trialMinutesLeft": 0,
    "alertTriggered": false,
    "chatMessagesUsed": 1840,
    "includedChatMessages": 4000,
    "chatUsagePercentage": 46,
    "channels": [
      "voice",
      "whatsapp",
      "webchat",
      "email"
    ],
    "excludedMetrics": []
  }
}
```

**SDK Code**

```python Growth plan drawing on a 500-minute pack
import requests

url = "https://api.jelliu.co/api/billing/usage"

headers = {"Authorization": "Bearer <token>"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript Growth plan drawing on a 500-minute pack
const url = 'https://api.jelliu.co/api/billing/usage';
const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Growth plan drawing on a 500-minute pack
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/billing/usage"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Growth plan drawing on a 500-minute pack
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/billing/usage")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java Growth plan drawing on a 500-minute pack
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.jelliu.co/api/billing/usage")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php Growth plan drawing on a 500-minute pack
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.jelliu.co/api/billing/usage', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp Growth plan drawing on a 500-minute pack
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/billing/usage");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Growth plan drawing on a 500-minute pack
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/billing/usage")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Starter plan in overage

**Response**

```json
{
  "data": {
    "minutesUsed": 236,
    "includedMinutes": 200,
    "bonusMinutes": 0,
    "overageMinutes": 36,
    "overageCostCents": 1620,
    "usagePercentage": 118,
    "isTrialing": false,
    "trialMinutesLeft": 0,
    "alertTriggered": true,
    "chatMessagesUsed": 1195,
    "includedChatMessages": 1200,
    "chatUsagePercentage": 99.58,
    "channels": [
      "voice",
      "whatsapp",
      "webchat",
      "email"
    ],
    "excludedMetrics": []
  }
}
```

**SDK Code**

```python Starter plan in overage
import requests

url = "https://api.jelliu.co/api/billing/usage"

headers = {"Authorization": "Bearer <token>"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript Starter plan in overage
const url = 'https://api.jelliu.co/api/billing/usage';
const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Starter plan in overage
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/billing/usage"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Starter plan in overage
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/billing/usage")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java Starter plan in overage
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.jelliu.co/api/billing/usage")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php Starter plan in overage
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.jelliu.co/api/billing/usage', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp Starter plan in overage
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/billing/usage");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Starter plan in overage
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/billing/usage")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```

### Trialing workspace

**Response**

```json
{
  "data": {
    "minutesUsed": 6,
    "includedMinutes": 15,
    "bonusMinutes": 0,
    "overageMinutes": 0,
    "overageCostCents": 0,
    "usagePercentage": 40,
    "isTrialing": true,
    "trialMinutesLeft": 9,
    "alertTriggered": false,
    "chatMessagesUsed": 42,
    "includedChatMessages": 4000,
    "chatUsagePercentage": 1.05,
    "channels": [
      "voice",
      "whatsapp",
      "webchat",
      "email"
    ],
    "excludedMetrics": []
  }
}
```

**SDK Code**

```python Trialing workspace
import requests

url = "https://api.jelliu.co/api/billing/usage"

headers = {"Authorization": "Bearer <token>"}

response = requests.get(url, headers=headers)

print(response.json())
```

```javascript Trialing workspace
const url = 'https://api.jelliu.co/api/billing/usage';
const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};

try {
  const response = await fetch(url, options);
  const data = await response.json();
  console.log(data);
} catch (error) {
  console.error(error);
}
```

```go Trialing workspace
package main

import (
	"fmt"
	"net/http"
	"io"
)

func main() {

	url := "https://api.jelliu.co/api/billing/usage"

	req, _ := http.NewRequest("GET", url, nil)

	req.Header.Add("Authorization", "Bearer <token>")

	res, _ := http.DefaultClient.Do(req)

	defer res.Body.Close()
	body, _ := io.ReadAll(res.Body)

	fmt.Println(res)
	fmt.Println(string(body))

}
```

```ruby Trialing workspace
require 'uri'
require 'net/http'

url = URI("https://api.jelliu.co/api/billing/usage")

http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true

request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'

response = http.request(request)
puts response.read_body
```

```java Trialing workspace
import com.mashape.unirest.http.HttpResponse;
import com.mashape.unirest.http.Unirest;

HttpResponse<String> response = Unirest.get("https://api.jelliu.co/api/billing/usage")
  .header("Authorization", "Bearer <token>")
  .asString();
```

```php Trialing workspace
<?php
require_once('vendor/autoload.php');

$client = new \GuzzleHttp\Client();

$response = $client->request('GET', 'https://api.jelliu.co/api/billing/usage', [
  'headers' => [
    'Authorization' => 'Bearer <token>',
  ],
]);

echo $response->getBody();
```

```csharp Trialing workspace
using RestSharp;

var client = new RestClient("https://api.jelliu.co/api/billing/usage");
var request = new RestRequest(Method.GET);
request.AddHeader("Authorization", "Bearer <token>");
IRestResponse response = client.Execute(request);
```

```swift Trialing workspace
import Foundation

let headers = ["Authorization": "Bearer <token>"]

let request = NSMutableURLRequest(url: NSURL(string: "https://api.jelliu.co/api/billing/usage")! as URL,
                                        cachePolicy: .useProtocolCachePolicy,
                                    timeoutInterval: 10.0)
request.httpMethod = "GET"
request.allHTTPHeaderFields = headers

let session = URLSession.shared
let dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in
  if (error != nil) {
    print(error as Any)
  } else {
    let httpResponse = response as? HTTPURLResponse
    print(httpResponse)
  }
})

dataTask.resume()
```