> ## 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.

# Get Usage via API

> Retrieve ACU usage records for your organization programmatically via the TextQL Billing Usage API.

## Overview

TextQL exposes usage data through several channels. Each answers a different question — pick the right one before building.

| What you want | Where to get it |
| - | - |
| ACU spend or dollar cost, by user or time period | **Usage API** (`GET /v1/billing/usage`) — this page |
| The same ACU/cost data, queried interactively inside a chat | **TextQL Console connector** |
| All conversations across all users and sources | **Chat List API** (`POST /v1/chat/list`) |
| Tokens and cost per conversation | **GetLlmUsage** RPC |
| Message content of a conversation | **Chat API** (`GET /v2/chats/{id}`) |
| Raw token counts by model (reconcile against provider bill) | [Token Usage API](/core/admin/token-usage-api) — BYOK only |

***

## Data Sources Reference

### Usage API — `GET /v1/billing/usage`

ACU consumption aggregated by **user × time bucket**. Use for billing dashboards and spend tracking across teams.

**Contains:** `organization`, `email`, `category` (`llm_tokens`, `compute_hours`, `cell_executions`), `acu`, `cost_center`, `start_datetime`, `end_datetime`

**Does not contain:** per-conversation breakdown, raw token counts, cost in dollars

***

### TextQL Console connector

The broadest of the usage surfaces: a connector so ACU spend, raw token counts, and the sandbox billing rate can all be queried interactively inside Ana instead of polled over REST. Requires the **`billing:read`** permission; see [Role-Based Access Control (RBAC)](/core/admin/rbac) for how to grant it.

| Table | Columns |
| - | - |
| `acu_usage` | `start_datetime`, `end_datetime`, `organization`, `deployment`, `email`, `roles`, `category`, `acu`, `cost_center`, `acu_rate_per_1000_usd`, `workspace`, `feature`, `action` |
| `token_usage` | `start_datetime`, `end_datetime`, `organization`, `email`, `model`, `input_tokens`, `cache_read_input_tokens`, `cache_write_input_tokens`, `output_tokens`, `total_tokens`, `request_count`, `workspace` |
| `sandbox_usage_rate` | `acus_per_hour`: ACUs charged per hour of sandbox runtime (one row, empty if the rate cannot be resolved) |

* `email` and `roles` are dropped from the tables entirely for members without the org-wide `chat:read_private` permission, not nulled, so queries referencing them error with an unknown column. `acu_usage` loses both; `token_usage` loses `email`.
* Rows cover every organization in your org's tenant, falling back to your org alone when it has no tenant assigned; filter on `organization` for one.
* `organization` is the org name; `deployment` is the deployment's display name, empty when it cannot be resolved.
* `workspace` is the same value as `organization`, and `feature` and `action` split `cost_center` (`chat:message` becomes `chat` and `message`), matching the names in the console. The older columns stay for existing queries.
* Rows are hourly buckets in UTC, keyed on `start_datetime`, with `end_datetime` exclusive. Roll up to days or months in SQL.
* No data before 2025-01-01 UTC. Querying earlier does not error; those rows simply do not exist.
* `acu_rate_per_1000_usd` is the org's current rate in USD per 1000 ACUs, denormalized onto every row rather than the rate at that bucket; dollars = `acu * acu_rate_per_1000_usd / 1000`. That is an estimate, not an invoice amount: usage before a tenant's billing start date is billed at \$0 but priced here at today's rate, and the billing start date is not exposed to filter it out.
* Unlike the [Token Usage API](/core/admin/token-usage-api), `token_usage` is simply empty on a non-BYOK deployment, never an error.

**Does not contain:** message content. For that, use the Chat API below.

Use this when someone wants to ask Ana a question like "who used the most ACUs last month" directly in chat, rather than building against the REST API.

***

### Chat List API — `POST /v1/chat/list`

Returns all conversations in the org across every source. Requires an **admin API key** — a member-scoped key silently returns only that member's chats.

```bash theme={null}
curl -s -X POST 'https://app.textql.com/v1/chat/list' \
  -H "tql_api_key: $K" \
  -H 'Content-Type: application/json' \
  -d '{"memberOnly": false}'
```

**Contains:** `chat_id`, `title`, `source` (Slack, UI, playbook), `creator` (email), `created_at`, `updated_at`

**Does not contain:** token counts, cost, message content

**Key parameters:**

| Parameter | Type | Description |
| - | - | - |
| `memberOnly` | boolean | `false` returns all org chats; `true` (default) returns only the key holder's chats |
| `sources` | array | Filter by source: `"slack"`, `"ui"`, `"playbook"` |
| `limit` | int | Page size (default 50) |
| `offset` | int | Pagination offset |

***

### GetLlmUsage — per-conversation tokens and cost

Returns token breakdown and estimated cost for a specific chat. Requires an **admin API key** for `include_costs`.

```bash theme={null}
curl -s -X POST 'https://app.textql.com/rpc/public/textql.rpc.public.chat.ChatService/GetLlmUsage' \
  -H "tql_api_key: $K" \
  -H 'Content-Type: application/json' \
  -d '{"chat_id": "YOUR_CHAT_ID", "include_costs": true}'
```

**Contains:** one record per LLM call within the chat — `input_tokens`, `output_tokens`, `cache_creation_input_tokens`, `cache_read_input_tokens`, `model_name`, `timestamp`, and `estimated_cost` (admin only). Sum across the array for conversation totals.

**Does not contain:** ACU values, message content

**Typical pattern:** call `POST /v1/chat/list` to get all `chat_id`s, then call `GetLlmUsage` for each.

***

### Chat API — `GET /v2/chats/{id}`

Returns the full content of a single conversation including messages.

```bash theme={null}
curl 'https://app.textql.com/v2/chats/YOUR_CHAT_ID' \
  -H "tql_api_key: $K"
```

**Contains:** message content, source, participants, timestamps

**Does not contain:** token counts, cost

***

<Warning>
  The **TextQL Usage connector** (visible inside Ana) contains the same thread-level fields as the Chat List API but is designed for interactive querying inside Ana — not for backend polling or external integrations. It does **not** contain ACU values or dollar cost — for that, use the [TextQL Console connector](#textql-console-connector) above instead.
</Warning>

<Frame>
  <img src="https://mintcdn.com/textql/xKEht7LI4-qtrybJ/images/admin/usage%20connector.png?fit=max&auto=format&n=xKEht7LI4-qtrybJ&q=85&s=8a430b58218770596c65a93574ecd1f6" alt="TextQL Usage connector in chat" width="1784" height="596" data-path="images/admin/usage connector.png" />
</Frame>

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

## Authentication

All requests require a Bearer token. Your token is a Base64-encoded string in the format `{member_id}:{api_token}`, created in **Settings → Developers → API Keys**.

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'
```

## Your First Request

Fetch the last 90 days of usage for your organization with a single call:

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

**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",
      "category": "llm_tokens",
      "acu": 8241.05
    }
  ],
  "pagination": {
    "page": 1,
    "page_size": 100,
    "total_count": 3
  }
}
```

Each record represents one user's usage within one time bucket, for one category.

## Parameters

All parameters are optional query parameters on `GET /usage`. Unrecognized query parameters are rejected with `400 invalid_parameter` — in particular, the response fields `start_datetime` / `end_datetime` are not valid request filters; use `start_date` / `end_date`.

### Date Range

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

Timezones are expressed through the RFC 3339 UTC offset (e.g. `2026-07-04T20:00:00-04:00`). The offset of `start_date` (or of `end_date` when `start_date` is omitted) also controls:

* Alignment of `day` and `month` buckets (days start at local midnight for that offset)
* The offset used to render `start_datetime` / `end_datetime` in the response

Remember to URL-encode `+` in positive offsets as `%2B` (e.g. `2026-07-01T00:00:00%2B05:30`).

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

# Daily usage for the first week of July, aligned to US Eastern days
curl 'https://app.textql.com/v1/billing/usage?start_date=2026-07-01T00:00:00-04:00&end_date=2026-07-08T00:00:00-04:00' \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

### Granularity

Controls the size of each time bucket. Defaults to `day`.

| Value | Bucket size |
| - | - |
| `hour` | 1 hour |
| `day` | 1 calendar day (at the request's UTC offset) |
| `month` | 1 calendar month (at the request's UTC offset) |

```bash theme={null}
# Monthly rollup
curl 'https://app.textql.com/v1/billing/usage?granularity=month' \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

### Filtering

**By organization** — useful for tenants with multiple orgs:

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

**By user email** — case-insensitive:

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

**By usage category:**

| Category | What it measures |
| - | - |
| `llm_tokens` | LLM inference consumption |
| `compute_hours` | Python and execution environment usage |
| `cell_executions` | Individual cell runs |

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

**By cost center** — comma-separated; a row's `cost_center` is only set when its usage came from a source with one attached (e.g. a playbook), so most usage has no cost center to filter by:

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

Filters can be combined freely:

```bash theme={null}
# LLM usage for one user, by hour, for a specific week
curl 'https://app.textql.com/v1/billing/usage?email=m.chen@acme.com&category=llm_tokens&granularity=hour&start_date=2026-01-27T00:00:00Z&end_date=2026-02-03T00:00:00Z' \
  -H 'Authorization: Bearer YOUR_TOKEN'
```

### Sorting

Use the `sort` parameter to control result order. Prefix with `-` for descending.

| Value | Description |
| - | - |
| `start_datetime` | Oldest first |
| `-start_datetime` | Newest first (default) |
| `acu` | Lowest usage first |
| `-acu` | Highest usage first |
| `email` | Alphabetical by user |
| `-email` | Reverse alphabetical by user |

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

### Pagination

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

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

Use `pagination.total_count` in the response to calculate how many pages exist.

## Code Examples

**Python — fetch all records across pages:**

```python theme={null}
import requests

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

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

# Example: all LLM usage for January, grouped by month
usage = get_all_usage(
    start_date="2026-01-01T00:00:00Z",
    end_date="2026-02-01T00:00:00Z",
    granularity="month",
    category="llm_tokens",
)
```

**JavaScript:**

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

async function getUsage(params = {}) {
  const query = new URLSearchParams(params).toString();
  const res = await fetch(`${BASE}/usage?${query}`, {
    headers: { Authorization: `Bearer ${TOKEN}` },
  });
  return res.json();
}

// Example: top spenders this month
const data = await getUsage({
  granularity: "month",
  sort: "-acu",
  page_size: 10,
});
```

## Response Fields

| Field | Type | Description |
| - | - | - |
| `start_datetime` | string (RFC 3339, at the request's UTC offset) | Start of the time bucket, inclusive |
| `end_datetime` | string (RFC 3339, at the request's UTC offset) | End of the time bucket, exclusive |
| `organization` | string | Organization name |
| `email` | string | User email (lowercase). Empty if usage cannot be attributed to a specific user |
| `category` | string | Usage category |
| `acu` | number | Usage in ACUs. Records with zero or negative values are omitted |
| `cost_center` | string | Omitted from the record entirely when the usage has no cost center attached (most usage) |

## Error Reference

| Status | Code | Cause |
| - | - | - |
| 400 | `invalid_parameter` | Bad date range, unknown category, unknown org, invalid granularity, or unrecognized query parameter (e.g. `start_datetime` instead of `start_date`) |
| 401 | `unauthorized` | Token is missing, invalid, or expired |
| 403 | `forbidden` | Token does not have access to the requested organization's data |

## Troubleshooting & Support

| Problem | What to check |
| - | - |
| 401 on every request | Confirm your token is Base64-encoded in `{member_id}:{api_token}` format and hasn't expired |
| 403 on a specific org | Your API key may not have access to that organization — check your role in **Settings → Members** |
| Empty `data` array | The filters you applied may return no records — try widening the date range or removing category/email filters |
| `acu` values seem low | Records with zero or negative ACU are omitted; usage may also be split across multiple category buckets |
| Unknown org error | The `organization` value must match the org name exactly as it appears in TextQL |

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.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.