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

# Usage

> Read your account's usage, success rate and cost over any time range, grouped and filtered the way you need

The **Usage** API returns your account's usage: requests, Agent runs, successes, errors, success rate, bytes and cost. Request and run counts and success rates match the [Usage page](https://online.nimbleway.com/usage) on the Nimble API, Nimble IP and Web Search Agents tabs. The Usage page does not show cost.

Two endpoints:

* `GET /v2/usage` returns usage rows and totals for a time range.
* `GET /v2/usage/catalog` lists the services, products and configurations usage is reported under, with the keys to filter by.

The account comes from the API key. Any valid API key of the account works. No admin role is needed.

> [!NOTE]
>
> The SDKs and the CLI do not have a usage method yet. Call the endpoints over
> HTTP, as in the examples below.

> [!NOTE]
>
> `cost_usd` is the metered usage cost, not an invoice amount. On some
> fixed-price plans spend is not shown, so `cost_usd` is `null` in rows and
> totals. Handle `null` before doing arithmetic on it.

## Quick Start

### Example Request

A call with no parameters returns the last 7 days, per service.

```python Python theme={"system"}
import os

import requests

response = requests.get(
    "https://sdk.nimbleway.com/v2/usage",
    headers={"Authorization": f"Bearer {os.environ['NIMBLE_API_KEY']}"},
)
response.raise_for_status()
usage = response.json()

print(usage["totals"]["cost_usd"])
```

```typescript TypeScript theme={"system"}
const response = await fetch("https://sdk.nimbleway.com/v2/usage", {
  headers: { Authorization: `Bearer ${process.env.NIMBLE_API_KEY}` },
});
if (!response.ok) {
  throw new Error(`${response.status}: ${await response.text()}`);
}
const usage = await response.json();

console.log(usage.totals.cost_usd);
```

```go Go theme={"system"}
package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "os"
)

func main() {
    req, _ := http.NewRequest("GET", "https://sdk.nimbleway.com/v2/usage", nil)
    req.Header.Set("Authorization", "Bearer "+os.Getenv("NIMBLE_API_KEY"))

    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    var usage map[string]any
    if err := json.NewDecoder(resp.Body).Decode(&usage); err != nil {
        panic(err)
    }
    fmt.Println(usage["totals"].(map[string]any)["cost_usd"])
}
```

```bash cURL theme={"system"}
curl 'https://sdk.nimbleway.com/v2/usage' \
--header 'Authorization: Bearer <YOUR-API-KEY>'
```

### Example Response

```json theme={"system"}
{
  "data": [
    {
      "dimensions": { "service": { "key": "data", "name": "Data" } },
      "metrics": {
        "requests": 48210,
        "runs": null,
        "successes": 47533,
        "errors": 677,
        "success_rate": 0.986,
        "bytes_in": null,
        "bytes_out": null,
        "cost_usd": "61.42"
      }
    },
    {
      "dimensions": { "service": { "key": "ip", "name": "IP" } },
      "metrics": {
        "requests": 15384,
        "runs": null,
        "successes": 15102,
        "errors": 282,
        "success_rate": 0.9817,
        "bytes_in": 1843200,
        "bytes_out": 734003200,
        "cost_usd": "18.75"
      }
    },
    {
      "dimensions": { "service": { "key": "wsa", "name": "WSA" } },
      "metrics": {
        "requests": null,
        "runs": 412,
        "successes": 398,
        "errors": 14,
        "success_rate": 0.966,
        "bytes_in": null,
        "bytes_out": null,
        "cost_usd": "35.10"
      }
    }
  ],
  "totals": {
    "requests": 63594,
    "runs": 412,
    "successes": 63033,
    "errors": 973,
    "success_rate": 0.9848,
    "bytes_in": 1843200,
    "bytes_out": 734003200,
    "cost_usd": "115.27"
  },
  "meta": {
    "start": "2026-10-30T00:00:00Z",
    "end": "2026-11-06T00:00:00Z",
    "granularity": "day",
    "range_snapped": false,
    "partial": true,
    "last_usage_at": "2026-11-05T13:00:00Z",
    "truncated": false,
    "excluded_services": [],
    "total": 3
  }
}
```

## How it works

**1. Pick a time range**

Set `start` and `end`, or leave them out for the last 7 days. The range can
be up to 90 days, or up to 365 days with `granularity=month`.

**2. Choose how to split the usage**

`group_by` sets the dimensions of each row: time, service, product,
configuration, API key, domain or country. The default is one row per
service.

**3. Narrow it down with filters**

Filter by service, product or configuration keys from `GET
        /v2/usage/catalog`, or by API key, domain or country.

**4. Read rows, totals and meta**

`data` holds the rows. `totals` covers the whole range and filters, across
every row and page. `meta` says which range was answered and whether the
figures are final. Figures for recent periods can still change.

## Parameters

All parameters are optional query parameters. Parameters marked **repeatable** take several values: repeat the parameter (`group_by=time&group_by=service`). A comma-separated list is not split: for `group_by`, `service`, `product` and `configuration` it returns 422, and for `domain` and `country` it is read as one value and matches nothing.

> [accordion] start
>
> **start** `string`
>
> Start of the range, ISO 8601. UTC when no offset is given.
>
> **Default:** `end` minus 7 days (24 hours for `granularity=hour`), rounded down to a whole bucket.
>
> **Example:** `2026-10-27T00:00:00Z`

> [accordion] end
>
> **end** `string`
>
> End of the range, ISO 8601. UTC when no offset is given. `end - start` can be at most 90 days, or 365 days with `granularity=month`, measured on the range as requested, before it is widened to whole buckets.
>
> **Default:** the end of the current `granularity` bucket in UTC: the next whole hour, midnight or month start.
>
> **Example:** `2026-11-03T00:00:00Z`

> [accordion] granularity
>
> **granularity** `string` (default: day)
>
> Time bucket size for `group_by=time`. All buckets are UTC.
>
> **Options:**
>
> * `hour`: only for ranges of 48 hours or less
> * `day`: ranges up to 90 days
> * `month`: ranges up to 365 days
>
> The range is widened to whole buckets. `meta.start` and `meta.end` report the range that was answered.

> [accordion] group\_by (repeatable)
>
> **group\_by** `string[]` (default: service)
>
> Dimensions to split usage by. At most 50 values.
>
> **Options:**
>
> * `time`: one row per `granularity` bucket
> * `service`, `product`, `configuration`: catalog keys. Grouping by `configuration` also returns its product.
> * `api_key`: the API key id
> * `domain`: the target domain. Only the top 100 domains are broken out. The rest are combined in one row with `domain: null`, and `meta.domains_capped` is `true`.
> * `country`: the target country, ISO 3166-1 alpha-2

> [accordion] service, product, configuration (repeatable)
>
> **service** `string[]`
>
> Only usage of these services. Keys come from `GET /v2/usage/catalog`.
>
> **Example:** `data`
>
> **product** `string[]`
>
> Only usage of these products. Keys come from `GET /v2/usage/catalog`.
>
> **Example:** `extractApi`
>
> **configuration** `string[]`
>
> Only usage of these configurations, each written `<product key>.<configuration key>`, because a configuration key is unique only within its product. Copy the `filter` value from `GET /v2/usage/catalog`.
>
> **Example:** `extractApi.vx6`

> [accordion] api\_key, domain, country (repeatable)
>
> **api\_key** `string[]`
>
> Only usage made with these API key ids (32 lowercase hex characters), or `none` for usage made without an API key.
>
> **domain** `string[]`
>
> Only usage against these target domains. Matching is case-insensitive. `www.` is not stripped, so use the exact host (`www.example.com` does not match `example.com`). A domain outside the top 100 returns no rows.
>
> **Example:** `example.com`
>
> **country** `string[]`
>
> Only usage targeting these countries, ISO 3166-1 alpha-2. Matching is case-insensitive.
>
> **Example:** `US`
>
> Some services cannot be split or filtered by `api_key`, `domain` or `country`. When you use one of these, those services are left out and listed in `meta.excluded_services`.

> [accordion] limit, offset
>
> **limit** `integer` (default: 100)
>
> Maximum number of rows returned, 1 to 200.
>
> **offset** `integer` (default: 0)
>
> Number of rows to skip. Page while `offset + limit < meta.total`.

Each repeatable parameter takes at most 50 values. Values of one parameter match any of them. Different parameters must all match.

## Response

> [accordion] data
>
> **data** `object[]`
>
> One row per combination of the `group_by` dimensions. Rows are ordered oldest bucket first, and largest usage first within a bucket. Ties keep a fixed order, so paging with `limit` and `offset` is stable.
>
> **row**
>
> **timestamp** `string`
>
> Start of the time bucket (UTC). Present only when `group_by` includes `time`.
>
> **dimensions** `object`
>
> Only the dimensions in `group_by` are present. `service`, `product` and `configuration` are `{ "key", "name" }` objects. `api_key`, `domain` and `country` are strings, or `null` when not recorded. The `domain` row combining every domain outside the top 100 is also `null`.
>
> **metrics** `object`
>
> The usage figures of this row, with the same fields as `totals`.

> [accordion] totals
>
> **totals** `object`
>
> Figures for the whole range and filters, across every row, whatever `limit` and `offset` are. A metric a service does not measure is `null`, not 0.
>
> **metrics**
>
> **requests** `integer | null`
>
> Requests made. `null` for usage counted in runs only.
>
> **runs** `integer | null`
>
> Agent runs. `null` for usage counted in requests only.
>
> **successes** `integer`
>
> Successful requests and runs.
>
> **errors** `integer`
>
> Requests and runs that did not succeed.
>
> **success\_rate** `number | null`
>
> `successes` divided by `requests` plus `runs`, from 0 to 1. `null` when there were none.
>
> **bytes\_in** `integer | null`
>
> Request bytes sent. `null` when the service does not measure it.
>
> **bytes\_out** `integer | null`
>
> Response bytes received. `null` when the service does not measure it.
>
> **cost\_usd** `string | null`
>
> Metered usage cost in US dollars, as a decimal string. It is not an invoice amount. Row costs are rounded to the cent, so rows may not add up exactly to `totals.cost_usd`. `null` in rows and totals on some fixed-price plans, where spend is not shown.

> [accordion] meta
>
> **meta** `object`
>
> **properties**
>
> **start** `string`
>
> Start of the range answered (UTC): the requested start rounded down to a whole bucket.
>
> **end** `string`
>
> End of the range answered (UTC): the requested end rounded up to a whole bucket.
>
> **granularity** `string`
>
> Time bucket size used.
>
> **range\_snapped** `boolean`
>
> `true` when the requested range did not fall on bucket boundaries, so it was widened to whole buckets.
>
> **partial** `boolean`
>
> `true` when figures may still change. Recent periods are still loading: for `hour`, the last hour or two. For `day`, today, and yesterday until about 05:00 UTC. For `month`, the current month, and the previous month on the 1st to the 3rd. It is also `true` when part of the usage could not be read this time.
>
> **domains\_capped** `boolean`
>
> Present, as `true`, only when a row combines the domains outside the top 100 (`domain: null`).
>
> **last\_usage\_at** `string | null`
>
> Most recent usage recorded for the account (UTC) in the services queried, at any time. `null` when there is none.
>
> **truncated** `boolean`
>
> `true` when the range held more rows than one call can read. Rows are cut and `totals` may be incomplete. Narrow the range, the filters or `group_by`.
>
> **excluded\_services** `string[]`
>
> Service keys left out because they cannot be split or filtered by `api_key`, `domain` or `country`. Their usage is not in `data` or `totals`.
>
> **total** `integer`
>
> Number of rows before `limit` and `offset`.

## Usage catalog

`GET /v2/usage/catalog` lists every service, product and configuration usage is reported under. Use the keys as `service`, `product` and `configuration` filters. Keys never change. Names can change, so match on keys in code.

Retired configurations stay in the list, because past usage carries their keys.

```bash cURL theme={"system"}
curl 'https://sdk.nimbleway.com/v2/usage/catalog' \
--header 'Authorization: Bearer <YOUR-API-KEY>'
```

Response (trimmed):

```json theme={"system"}
{
  "services": [
    {
      "key": "data",
      "name": "Data",
      "products": [
        {
          "key": "extractApi",
          "name": "Extract API",
          "configurations": [
            {
              "key": "vx6",
              "filter": "extractApi.vx6",
              "name": "Extract API VX6",
              "status": "active"
            },
            {
              "key": "vx10",
              "filter": "extractApi.vx10",
              "name": "Extract API VX10",
              "status": "active"
            }
          ]
        },
        {
          "key": "searchApi",
          "name": "Search API",
          "configurations": [
            {
              "key": "answer",
              "filter": "searchApi.answer",
              "name": "Search API Answer",
              "status": "deprecated"
            }
          ]
        }
      ]
    },
    {
      "key": "ip",
      "name": "IP",
      "products": [
        {
          "key": "proxyApi",
          "name": "Proxy API",
          "configurations": [
            {
              "key": "residential",
              "filter": "proxyApi.residential",
              "name": "Residential Proxy",
              "status": "active"
            }
          ]
        }
      ]
    },
    {
      "key": "wsa",
      "name": "WSA",
      "products": [
        {
          "key": "agentApi",
          "name": "Agent API",
          "configurations": [
            {
              "key": "high",
              "filter": "agentApi.high",
              "name": "Agent API High",
              "status": "active"
            }
          ]
        }
      ]
    }
  ]
}
```

A configuration's `status` is one of:

* `active`: on sale.
* `deprecated`: still billed, not sold to new customers.
* `retired`: no longer billed. Listed because past usage carries its key.

## Common recipes

### Totals for a date range

Read `totals`. It covers the whole range whatever `limit` is.

```bash theme={"system"}
curl 'https://sdk.nimbleway.com/v2/usage?start=2026-10-01T00:00:00Z&end=2026-11-01T00:00:00Z' \
--header 'Authorization: Bearer <YOUR-API-KEY>'
```

### Daily chart

Group by `time` to get one row per day. Add `service` to get one series per service.

```bash theme={"system"}
curl 'https://sdk.nimbleway.com/v2/usage?group_by=time&group_by=service&start=2026-10-01T00:00:00Z&end=2026-11-01T00:00:00Z' \
--header 'Authorization: Bearer <YOUR-API-KEY>'
```

Each row carries the bucket start in `timestamp`:

```json theme={"system"}
{
  "timestamp": "2026-10-01T00:00:00Z",
  "dimensions": { "service": { "key": "data", "name": "Data" } },
  "metrics": {
    "requests": 6890,
    "runs": null,
    "successes": 6801,
    "errors": 89,
    "success_rate": 0.9871,
    "bytes_in": null,
    "bytes_out": null,
    "cost_usd": "8.77"
  }
}
```

For an hourly chart, use `granularity=hour`. With no `start`, it covers the last 24 hours. Use `granularity=month` for ranges longer than 90 days, up to 365.

A month of daily rows per service can exceed 100 rows. Raise `limit` up to 200, or page with `offset` while `offset + limit < meta.total`.

### Breakdown by configuration

```bash theme={"system"}
curl 'https://sdk.nimbleway.com/v2/usage?group_by=configuration' \
--header 'Authorization: Bearer <YOUR-API-KEY>'
```

Each row shows the product with the configuration:

```json theme={"system"}
{
  "dimensions": {
    "product": { "key": "extractApi", "name": "Extract API" },
    "configuration": { "key": "vx6", "name": "Extract API VX6" }
  },
  "metrics": {
    "requests": 31250,
    "runs": null,
    "successes": 30912,
    "errors": 338,
    "success_rate": 0.9892,
    "bytes_in": null,
    "bytes_out": null,
    "cost_usd": "39.06"
  }
}
```

Older usage under a name the catalog does not map shows with `product: null` and `configuration.key: null`. The configuration `name` is then the legacy name. When no name was recorded at all, `configuration` is `null`. That usage cannot be filtered by product or configuration.

### Breakdown by domain, country or API key

```bash theme={"system"}
curl 'https://sdk.nimbleway.com/v2/usage?group_by=domain&limit=20' \
--header 'Authorization: Bearer <YOUR-API-KEY>'
```

Only the top 100 domains are broken out. The rest are combined in one row with `domain: null`, and `meta.domains_capped` is `true`.

Swap `domain` for `country` or `api_key` for the other breakdowns. Not every service records these dimensions. Check `meta.excluded_services` for the services left out of the result:

```json theme={"system"}
"excluded_services": ["wsa"]
```

### Filter by a catalog key

Copy keys from `GET /v2/usage/catalog`. This returns daily Extract API VX6 usage:

```bash theme={"system"}
curl 'https://sdk.nimbleway.com/v2/usage?configuration=extractApi.vx6&group_by=time' \
--header 'Authorization: Bearer <YOUR-API-KEY>'
```

Other examples: `service=wsa` for Agent runs only, or `product=searchApi&product=extractApi` for two products.

### Usage of one API key

Find the key id with `group_by=api_key`, then filter on it:

```bash theme={"system"}
curl 'https://sdk.nimbleway.com/v2/usage?api_key=<API-KEY-ID>&group_by=time' \
--header 'Authorization: Bearer <YOUR-API-KEY>'
```

Pass `api_key=none` for usage made without an API key.

## Ask your agent

AI agents can read your usage through the [Nimble MCP server](https://docs.nimbleway.com/integrations/mcp-server/mcp-server.md) at `https://mcp.nimbleway.com/mcp`. Two tools wrap these endpoints:

| Tool | Endpoint | What it does |
| - | - | - |
| `nimble_usage` | `GET /v2/usage` | Usage, success rate and cost for a range, with the same parameters. A bare call returns one row per service with no time split |
| `nimble_usage_catalog` | `GET /v2/usage/catalog` | Lists the service, product and configuration keys |

The tools tell the agent to look up keys in the catalog before filtering, and to say so when `meta.partial` means figures may still change. Example prompts:

* "How many requests did I make last week and what was my success rate?"
* "What did I spend on Nimble in October, broken down by product?"
* "Show my daily Agent runs for the last 30 days."
* "Which domains had the most errors in the last 24 hours?"

## Errors

Errors from the usage endpoints return a JSON body. `detail.message` says what was wrong and how to fix the call. `detail.extra.error_type` is a stable code to branch on.

| Status | `error_type` | When |
| - | - | - |
| 401 | | Missing or invalid API key (plain-text body) |
| 401 or 403 | `usage_token_rejected` | The key was accepted but is not authorized to read usage (JSON body) |
| 422 | `invalid_usage_query` | A parameter cannot be answered as given: unknown key, `start` not before `end`, range over 90 days (365 with `granularity=month`), `hour` over 48 hours, more than 50 values, an `api_key` that is not a 32-character key ID or `none`, a `configuration` not written `<product>.<configuration>`, or a query too broad to answer at once |
| 502 | `usage_read_failed` | Usage could not be read. Retry shortly |
| 503 | `usage_catalog_unavailable` | Usage names are temporarily unavailable. Retry in a few seconds |

A value of the wrong type, such as `granularity=week` or `limit=500`, or an unknown query parameter, returns 422 with `error_type` `validation_error` and the failing fields in `detail.extra.errors`. A comma-separated `group_by=time,service` fails this way: repeat the parameter instead.

An unknown key lists the valid ones:

```json theme={"system"}
{
  "detail": {
    "message": "unknown service key(s): nope. Valid keys: data, ip, wsa. GET /v2/usage/catalog lists them with their names.",
    "extra": {
      "nimble_request_id": "dcdf2e2e0fe5403684e56be76ff56d4b",
      "error_type": "invalid_usage_query",
      "parameter": "service",
      "invalid": ["nope"],
      "valid": ["data", "ip", "wsa"]
    }
  }
}
```

A range that is too long says where to move `start`:

```json theme={"system"}
{
  "detail": {
    "message": "range is 158 days; the maximum is 90 — move start to 2026-08-08T00:00:00Z or later, or use granularity=month (up to 365 days)",
    "extra": {
      "nimble_request_id": "5b0c1f7e9a2d4c6e8f1a3b5d7c9e0f12",
      "error_type": "invalid_usage_query",
      "parameter": "start",
      "max_days": 90
    }
  }
}
```

## Next Steps

> [card] [MCP Server](https://docs.nimbleway.com/integrations/mcp-server/mcp-server.md)
>
> Connect your AI client to Nimble and ask about your usage in plain language.

> [card] [Pricing](https://docs.nimbleway.com/nimble-sdk/admin/pricing.md)
>
> See how each product and configuration is billed.

> [card] [Rate Limits](https://docs.nimbleway.com/nimble-sdk/admin/rate-limits.md)
>
> Limits that apply to your account's requests.

> [card] [API Introduction](https://docs.nimbleway.com/api-reference/introduction.md)
>
> Authentication and the rest of the v2 API.


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