> This page is for version v2 (default).
> For other versions, use one of these documentation indexes:
> - v2 (default): https://docs.paid.ai/v-2/llms.txt
> - v1: https://docs.paid.ai/v-1/llms.txt

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

# Analytics API overview

> Query your Paid data with SQL to power dashboards and reports

The Analytics API lets you run read-only SQL queries directly against your Paid
data, including usage signals, AI costs, revenue, credits, and delivered value.
Use it to build your own dashboards, reports, and in-product analytics without
exporting data first.

Every query is automatically scoped to your organization: you can only ever see
your own data, and no organization filter is needed (or possible) in your SQL.

## Prerequisites

* Have your [API key](https://app.paid.ai/settings/api-keys) ready.

## The data model

Your data is exposed as a small [star schema](https://en.wikipedia.org/wiki/Star_schema)
of **dimension** tables (`dim_*`, describing entities) and **fact** tables
(`fact_*`, describing events and transactions). Facts join to dimensions on
`*_id` columns.

| Group                 | Tables                                                                                                                                   | What they hold                                                                |
| --------------------- | ---------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- |
| Dimensions            | `dim_customer`, `dim_customer_unit`, `dim_customer_unit_ancestor`, `dim_product`, `dim_plan`, `dim_credits_currency`, `dim_organization` | Your customers and their units, products/agents, plans, and credit currencies |
| Usage                 | `fact_signal`                                                                                                                            | One row per signal (event), including its raw JSON `data` payload             |
| Cost                  | `fact_cost`                                                                                                                              | One row per AI/LLM call: vendor, model, tokens, cost                          |
| Value                 | `fact_delivered_value`                                                                                                                   | Business value delivered per signal                                           |
| Revenue               | `fact_invoice`, `fact_invoice_line`, `fact_order`, `fact_order_line`, `fact_payment`, `fact_usage_summary`                               | Contracts, invoices, payments, and usage per billing window                   |
| Credits               | `fact_credit_transaction`                                                                                                                | Credit grants and spends                                                      |
| Unit caps and history | `dim_customer_unit_cap`, `dim_customer_unit_cap_current`, `fact_customer_unit_event`                                                     | Spend caps on customer units and every change to a unit                       |

See the [Schema reference](./schema-reference) for every column, and the
[Query cookbook](./query-cookbook) for ready-to-use examples.

## Endpoints

The base URL is `https://api.agentpaid.io/api/v2/analytics`. All
endpoints authenticate with your API key via the `Authorization: Bearer` header.

### `POST /query`

Run a single SQL statement. The request body is `{ "query": "<sql>" }`.

```bash
curl -X POST https://api.agentpaid.io/api/v2/analytics/query \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": "SELECT signal_name, count() AS signals FROM fact_signal WHERE created_at > now() - INTERVAL 7 DAY GROUP BY signal_name ORDER BY signals DESC"}'
```

The response gives you typed columns, rows in column order, and metadata:

```json
{
  "columns": [
    { "name": "signal_name", "type": "String" },
    { "name": "signals", "type": "UInt64" }
  ],
  "rows": [
    ["addin-chat", "4514784"],
    ["email-triage", "1379812"]
  ],
  "meta": { "rowCount": 2, "elapsedMs": 190, "truncated": false }
}
```

### `GET /schema`

Returns the queryable views with their columns, types, and descriptions. This is
the same information as the [Schema reference](./schema-reference), live from your
account. Useful for discovery and for building tooling.

```bash
curl https://api.agentpaid.io/api/v2/analytics/schema \
  -H "Authorization: Bearer YOUR_API_KEY"
```

### `GET /signals/metadata`

Signals carry a free-form JSON `data` payload whose shape you define. This
endpoint lists the JSON paths (and their observed types) present in your
signals, grouped by signal name, so you know what you can filter and group on.

| Query parameter | Description                                              |
| --------------- | -------------------------------------------------------- |
| `signalName`    | Restrict discovery to a single signal name. Optional.    |
| `fromDate`      | Start of the window (ISO 8601). Defaults to 30 days ago. |
| `toDate`        | End of the window (ISO 8601). Defaults to now.           |

```bash
curl "https://api.agentpaid.io/api/v2/analytics/signals/metadata?signalName=email-triage" \
  -H "Authorization: Bearer YOUR_API_KEY"
```

```json
{
  "signals": [
    {
      "signalName": "email-triage",
      "paths": [
        { "path": "account_id", "types": ["String"] },
        { "path": "tool_name", "types": ["String"] }
      ]
    }
  ],
  "meta": {
    "fromDate": "2026-05-13T00:00:00.000Z",
    "toDate": "2026-06-12T00:00:00.000Z"
  }
}
```

## Writing queries

The API speaks [ClickHouse SQL](https://clickhouse.com/docs/en/sql-reference).
A few rules and conventions:

* **Read-only.** Only a single `SELECT` (or `WITH … SELECT`) statement is
  accepted. Anything else is rejected.
* **No org filter needed.** Queries are automatically restricted to your
  organization. Reference tables by their unqualified name (`FROM fact_signal`),
  not `analytics.fact_signal` or `default.*`.
* **Money is in cents.** Monetary columns are cents. Divide by 100 for display
  (e.g. `total_incl_tax / 100`). Most are integers; `fact_cost.cost_amount` is
  fractional cents (Decimal) since a single AI call usually costs less than a
  cent.
* **Credits are a third case: not money, not whole.** The
  `fact_credit_transaction` amount and balance columns are `Decimal(30, 6)`, so
  a single spend can be a fraction of a credit. They are counts of credits, not
  cents, so never divide them by 100.
* **64-bit integers come back as strings.** Counts, sums, amounts, and large
  ids (`UInt64`/`Int64`) are returned as JSON strings to preserve precision
  beyond 2^53. Parse them client-side as needed. Smaller integers, floats,
  booleans, and dates serialize natively.
* **Decimal columns come back as JSON numbers**, not strings (e.g. `9.5`). The
  64-bit quoting above does not apply to them, so a `Decimal` beyond 2^53 is
  already rounded by the time you receive it. Select `toString(col)` when you
  need its exact digits.

### Limits

> **Note**
>
> These limits are guardrails for interactive analytics. The API is not a bulk
> export mechanism. For large extracts, aggregate or paginate with
> `LIMIT`/`WHERE` on an indexed column such as `created_at`.

| Limit          | Value                                                     |
| -------------- | --------------------------------------------------------- |
| Execution time | 30 seconds per query                                      |
| Result rows    | 10,000 (responses set `meta.truncated: true` when capped) |
| Statements     | One `SELECT`/`WITH` per request                           |
| Concurrency    | A small number of simultaneous queries per organization   |

## Errors

Errors return a JSON body with an `error`, a machine-readable `code`, and
usually a `details` message.

| HTTP | `code`                       | Meaning                                                                                                   |
| ---- | ---------------------------- | --------------------------------------------------------------------------------------------------------- |
| 400  | `INVALID_QUERY`              | Not a single read-only `SELECT`/`WITH` statement                                                          |
| 400  | `QUERY_ERROR`                | The query was rejected by the database (syntax, unknown column, type mismatch, …); `details` explains why |
| 400  | `INVALID_DATE_RANGE`         | `fromDate` is after `toDate` (metadata endpoint)                                                          |
| 403  | `ACCESS_DENIED`              | The query referenced something outside the analytics views                                                |
| 408  | `QUERY_TIMEOUT`              | The query exceeded the 30-second limit                                                                    |
| 429  | `CONCURRENCY_LIMIT_EXCEEDED` | Too many concurrent queries; retry shortly                                                                |
| 401  | None                         | Missing or invalid API key                                                                                |

## Deprecated experimental path

The former `/api/v2/experimental/analytics/*` path is deprecated and is not
supported for new integrations. It remains available for existing integrations;
use `/api/v2/analytics/*` instead.

## Related

* [Schema reference](./schema-reference) for every table and column you can query.
* [Query cookbook](./query-cookbook) for ready-to-use SQL recipes.
* [Execute a SQL query](/api-reference/api-reference/analytics/execute-analytics-query) in the API reference.