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

# BYOK Token Usage

> Retrieve raw LLM token usage by organization, member, and model programmatically. BYOK deployments only.

## Overview

The Token Usage API returns **raw LLM token counts** aggregated by time bucket, organization, member, and model. Use it to reconcile usage against your own model provider (AWS Bedrock, Anthropic, etc.), build cost dashboards, or audit inference by user and model.

This API reports **tokens**, not ACUs. For ACU-based billing data, use the [Usage API](/core/admin/usage-api) (`GET /v1/billing/usage`).

**Availability:** BYOK deployments only. Deployments that use the TextQL-managed model provider cannot access this API.

**Base URL:** `https://app.textql.com/v1/billing`

**Endpoint:** `GET /v1/billing/token-usage`

***

## Authentication

Same as the Usage API. All requests require an API key with **`billing:read`** permission.

Create keys in **Settings → Developers → API Keys**. The token is Base64-encoded `{member_id}:{api_token}`.

Pass it in one of two ways:

```bash theme={null}
# Authorization header
-H 'Authorization: Bearer YOUR_TOKEN'

# Or as a custom header
-H 'tql_api_key: YOUR_TOKEN'
```

The API key's organization determines **tenant scope**: results include all organizations in that tenant (unless filtered by `organization`).

***

## Your First Request

Fetch the last 90 days of token usage for your tenant:

```bash theme={null}
curl 'https://app.textql.com/v1/billing/token-usage' \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

**Example response:**

```json theme={null}
{
  "data": [
    {
      "start_datetime": "2026-01-31T00:00:00Z",
      "end_datetime": "2026-02-01T00:00:00Z",
      "organization": "acme-engineering",
      "email": "m.chen@acme.com",
      "model": "claude-sonnet-4-6",
      "input_tokens": 125000,
      "cache_read_input_tokens": 45000,
      "cache_write_input_tokens": 12000,
      "output_tokens": 38000,
      "total_tokens": 220000,
      "request_count": 142
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 100,
    "total_count": 1
  }
}
```

Each record is one **(time bucket × organization × member × model)** slice. Token fields are summed across all LLM requests in that slice.

***

## Query Parameters

All parameters are optional query parameters on `GET /token-usage`.

### Date range

| Parameter    | Format         | Default                   | Description           |
| ------------ | -------------- | ------------------------- | --------------------- |
| `start_date` | RFC 3339 (UTC) | 90 days before `end_date` | Inclusive lower bound |
| `end_date`   | RFC 3339 (UTC) | Current UTC time          | Exclusive upper bound |

**Constraints:**

* `start_date` must be **before** `end_date`.
* The window **`end_date − start_date`** cannot exceed **90 days**.

```bash theme={null}
curl 'https://app.textql.com/v1/billing/token-usage?start_date=2026-01-01T00:00:00Z&end_date=2026-02-01T00:00:00Z' \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

### Granularity

Controls time bucket size. Default: **`day`**.

| Value   | Bucket size            | `end_datetime` offset      |
| ------- | ---------------------- | -------------------------- |
| `hour`  | 1 hour                 | `start_datetime + 1 hour`  |
| `day`   | 1 calendar day (UTC)   | `start_datetime + 1 day`   |
| `month` | 1 calendar month (UTC) | `start_datetime + 1 month` |

```bash theme={null}
curl 'https://app.textql.com/v1/billing/token-usage?granularity=hour&start_date=2026-06-10T00:00:00Z&end_date=2026-06-11T00:00:00Z' \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

### Filtering

**By organization** — comma-separated **organization names** (not UUIDs). Omit to include all organizations in the tenant.

```bash theme={null}
curl 'https://app.textql.com/v1/billing/token-usage?organization=acme-engineering,acme-research' \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

Unknown organization names return **`400 invalid_parameter`**.

**By user email** — comma-separated, **case-insensitive**. Only members whose email matches (in the selected orgs) are included. If no members match, the response is an empty `data` array with `200 OK`.

```bash theme={null}
curl 'https://app.textql.com/v1/billing/token-usage?email=j.ramirez@acme.com,s.patel@acme.com' \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

**By model** — comma-separated model names as stored in usage records (e.g. `claude-sonnet-4-6`, `gpt-4o`). Exact match.

```bash theme={null}
curl 'https://app.textql.com/v1/billing/token-usage?model=claude-sonnet-4-6,gpt-4o' \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

Filters can be combined:

```bash theme={null}
curl 'https://app.textql.com/v1/billing/token-usage?organization=acme-engineering&email=m.chen@acme.com&model=claude-sonnet-4-6&granularity=day&start_date=2026-01-27T00:00:00Z&end_date=2026-02-03T00:00:00Z' \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

### Sorting

Use `sort` to order results **before pagination**. Prefix with `-` for descending. Default: **`-start_datetime`** (newest buckets first).

| Value             | Description                   |
| ----------------- | ----------------------------- |
| `start_datetime`  | Oldest bucket first           |
| `-start_datetime` | Newest bucket first (default) |
| `email`           | Alphabetical by user email    |
| `-email`          | Reverse alphabetical by email |
| `model`           | Alphabetical by model name    |
| `-model`          | Reverse alphabetical by model |
| `total_tokens`    | Lowest total tokens first     |
| `-total_tokens`   | Highest total tokens first    |

Tie-break order (when primary sort values match): email → model → start\_datetime → organization → total\_tokens.

```bash theme={null}
curl 'https://app.textql.com/v1/billing/token-usage?sort=-total_tokens' \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

### Pagination

| Parameter   | Default | Max  |
| ----------- | ------- | ---- |
| `page_size` | 100     | 1000 |
| `page`      | 1       | —    |

```bash theme={null}
curl 'https://app.textql.com/v1/billing/token-usage?page=2&page_size=50' \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

Use `pagination.total_count` to compute total pages. Requesting a page beyond the last page returns an empty `data` array with the requested `page` and correct `total_count`.

***

## Response Fields

### Top level

| Field                    | Type    | Description                             |
| ------------------------ | ------- | --------------------------------------- |
| `data`                   | array   | Token usage records (see below)         |
| `pagination.page`        | integer | Current page (1-based)                  |
| `pagination.page_size`   | integer | Page size used for this response        |
| `pagination.total_count` | integer | Total matching records across all pages |

### Record object (`data[]`)

| Field                      | Type                   | Description                                                                          |
| -------------------------- | ---------------------- | ------------------------------------------------------------------------------------ |
| `start_datetime`           | string (UTC, RFC 3339) | Start of the time bucket, **inclusive**                                              |
| `end_datetime`             | string (UTC, RFC 3339) | End of the time bucket, **exclusive**                                                |
| `organization`             | string                 | Organization **name**                                                                |
| `email`                    | string                 | Member email, **lowercase**. Empty string if usage is **non-attributed** (see below) |
| `model`                    | string                 | Model name                                                                           |
| `input_tokens`             | integer                | Sum of input/prompt tokens                                                           |
| `cache_read_input_tokens`  | integer                | Sum of cache-read input tokens                                                       |
| `cache_write_input_tokens` | integer                | Sum of cache-write input tokens                                                      |
| `output_tokens`            | integer                | Sum of output/completion tokens                                                      |
| `total_tokens`             | integer                | Sum of all four token fields above                                                   |
| `request_count`            | integer                | Number of LLM usage events in this bucket                                            |

**`total_tokens` calculation:**

```
total_tokens = input_tokens + cache_read_input_tokens + cache_write_input_tokens + output_tokens
```

***

## Member Attribution

Usage is attributed to a member when `member_id` is present on the underlying usage event and the member's email is not a TextQL staff domain.

**Non-attributed usage** (empty `email`):

* Usage with no member attribution
* Usage from `@textql.com` or `*.textql.com` email addresses (TextQL staff)

Non-attributed rows are still returned with `"email": ""`. Clients may display these as "(non-attributed)" in UI.

***

## BYOK Requirement

This API is only available when your deployment uses **your own model provider** (BYOK — bring your own keys).

| Deployment type                  | Token Usage API                     |
| -------------------------------- | ----------------------------------- |
| BYOK (customer-managed provider) | Available                           |
| TextQL-managed provider          | **Not available** — `403 forbidden` |

**Error when not BYOK:**

```json theme={null}
{
  "code": "forbidden",
  "message": "token usage API is only available for BYOK deployments"
}
```

***

## Comparison with Usage API

|               | Usage API                                   | Token Usage API                                                                      |
| ------------- | ------------------------------------------- | ------------------------------------------------------------------------------------ |
| Path          | `GET /v1/billing/usage`                     | `GET /v1/billing/token-usage`                                                        |
| Metric        | ACU                                         | Raw token counts                                                                     |
| Grouping      | category (`llm_tokens`, `compute_hours`, …) | model name                                                                           |
| BYOK required | No                                          | **Yes**                                                                              |
| Extra fields  | `category`, `acu`, `cost_center`            | `model`, `input_tokens`, `cache_*`, `output_tokens`, `total_tokens`, `request_count` |
| Sort options  | includes `acu` / `-acu`                     | includes `total_tokens`, `model`                                                     |

Shared behavior: auth, tenant scoping, `organization` / `email` filters, date range defaults, 90-day max window, granularity, pagination.

***

## Code Examples

### Python — paginate all token usage for January

```python theme={null}
import requests

TOKEN = "your-token"
BASE = "https://app.textql.com/v1/billing"
headers = {"Authorization": f"Bearer {TOKEN}"}

def get_all_token_usage(**params):
    records = []
    page = 1
    while True:
        resp = requests.get(
            f"{BASE}/token-usage",
            headers=headers,
            params={**params, "page": page, "page_size": 1000},
        )
        resp.raise_for_status()
        data = resp.json()
        records.extend(data["data"])
        if len(records) >= data["pagination"]["total_count"]:
            break
        page += 1
    return records

usage = get_all_token_usage(
    start_date="2026-01-01T00:00:00Z",
    end_date="2026-02-01T00:00:00Z",
    granularity="day",
)
```

### JavaScript — top models by token volume (last 90 days)

```javascript theme={null}
const TOKEN = "your-token";
const BASE = "https://app.textql.com/v1/billing";

async function getTokenUsage(params = {}) {
  const query = new URLSearchParams(params).toString();
  const res = await fetch(`${BASE}/token-usage?${query}`, {
    headers: { Authorization: `Bearer ${TOKEN}` },
  });
  if (!res.ok) throw new Error(await res.text());
  return res.json();
}

const data = await getTokenUsage({
  granularity: "month",
  sort: "-total_tokens",
  page_size: 20,
});
```

***

## Error Reference

Errors use the same envelope as the Usage API:

```json theme={null}
{
  "code": "error_code",
  "message": "Human-readable description"
}
```

| HTTP status | `code`              | Typical cause                                                                                                                                                                          |
| ----------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400         | `invalid_parameter` | Invalid date format; `start_date` ≥ `end_date`; date range > 90 days; invalid `granularity` or `sort`; invalid email format; unknown `organization` name; invalid `page` / `page_size` |
| 403         | `forbidden`         | Missing/invalid/expired API key; insufficient permissions; **or** deployment is not BYOK                                                                                               |
| 500         | `internal_error`    | Server-side failure retrieving usage                                                                                                                                                   |

**Note:** Authentication failures return **403** (not 401) with `code: "forbidden"`.

***

## Troubleshooting & Support

| Problem                                                      | What to check                                                                                        |
| ------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
| 403 `token usage API is only available for BYOK deployments` | Deployment must use customer-managed model keys, not TextQL-managed provider                         |
| 403 on every request                                         | Token format (`{member_id}:{api_token}` Base64), expiry, and `billing:read` permission               |
| Empty `data`                                                 | Widen date range; remove `email` / `model` filters; confirm LLM usage exists for the tenant          |
| Unknown organization                                         | `organization` must be the **exact org name** as shown in TextQL, not a UUID                         |
| `email` is empty on some rows                                | Expected for non-attributed usage (unattributed events or TextQL staff)                              |
| Counts differ from provider bills                            | This API reflects TextQL-recorded usage; provider billing may use different bucketing or attribution |

If you're still running into issues, contact support at [support@textql.com](mailto:support@textql.com) and include the full request URL and response body.
