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

# Query Usage and Spending

>  - Query spending and request statistics for a specified time range
- Filter by model and group by model or calendar day
- Query usage for the current API Key or the entire account
- Returns USD amounts, credits, request counts, and token usage 

Use an API Key to query spending within a specified time range, filter by model, and group by model, calendar day, or both. This endpoint returns aggregated results directly; no task creation or task-status polling is required.

<RequestExample>
  ```bash cURL theme={null}
  curl --get 'https://api.apimart.ai/v1/usage' \
    --header 'Authorization: Bearer <token>' \
    --data-urlencode 'start=2026-09-01T00:00:00+08:00' \
    --data-urlencode 'end=2026-09-08T00:00:00+08:00' \
    --data-urlencode 'group_by=model'
  ```

  ```python Python theme={null}
  import requests

  response = requests.get(
      "https://api.apimart.ai/v1/usage",
      headers={"Authorization": "Bearer <token>"},
      params={
          "start": "2026-09-01T00:00:00+08:00",
          "end": "2026-09-08T00:00:00+08:00",
          "group_by": "model",
      },
      timeout=30,
  )

  payload = response.json()
  if not response.ok or not payload.get("success"):
      message = payload.get("error", {}).get("message", "Usage query failed")
      raise RuntimeError(f"HTTP {response.status_code}: {message}")

  print(payload["data"]["total"])
  print(payload["data"]["items"])
  ```

  ```javascript JavaScript theme={null}
  const url = new URL("https://api.apimart.ai/v1/usage");
  url.search = new URLSearchParams({
    start: "2026-09-01T00:00:00+08:00",
    end: "2026-09-08T00:00:00+08:00",
    group_by: "model",
  }).toString();

  const response = await fetch(url, {
    headers: { Authorization: "Bearer <token>" },
  });
  const payload = await response.json();

  if (!response.ok || !payload.success) {
    throw new Error(
      `HTTP ${response.status}: ${payload.error?.message ?? "Usage query failed"}`,
    );
  }

  console.log(payload.data.total);
  console.log(payload.data.items);
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "success": true,
    "data": {
      "scope": "key",
      "start": 1788192000,
      "end": 1788796800,
      "tz": "Asia/Shanghai",
      "group_by": "model",
      "total": {
        "amount_usd": 12.3456,
        "credits": 123.456,
        "requests": 1834,
        "prompt_tokens": 902311,
        "completion_tokens": 215044
      },
      "items": [
        {
          "model": "gpt-5.6-luna",
          "amount_usd": 9.1271,
          "credits": 91.271,
          "requests": 1520,
          "prompt_tokens": 880120,
          "completion_tokens": 201300
        },
        {
          "model": "sora-2",
          "amount_usd": 3.2185,
          "credits": 32.185,
          "requests": 314,
          "prompt_tokens": 22191,
          "completion_tokens": 13744
        }
      ]
    }
  }
  ```

  ```json 400 theme={null}
  {
    "success": false,
    "error": {
      "code": "range_too_large",
      "message": "Time range must not exceed 31 days.",
      "type": "usage_query_error"
    }
  }
  ```
</ResponseExample>

## Authentication

<ParamField header="Authorization" type="string" required>
  Authenticate with the same API Key used for model calls, using a Bearer Token. Get a key from the [API Key management page](https://apimart.ai/keys).

  ```
  Authorization: Bearer YOUR_API_KEY
  ```
</ParamField>

<Info>
  Queries are available even with a zero balance. This endpoint does not check the balance, but still checks API Key status, expiration, IP allowlists, and account status.
</Info>

## Endpoints

```text theme={null}
GET /v1/usage
GET /usage
```

Both endpoints provide the same functionality and support CORS. Keep your API Key private; do not expose it in public frontend code.

## Request parameters

All parameters are passed as URL query parameters.

<ParamField query="start" type="integer | string">
  Inclusive start time. Accepts a Unix timestamp in seconds or an RFC3339 string with a timezone, such as `2026-09-01T00:00:00+08:00`.

  Defaults to 24 hours before `end` when omitted. Timestamps use seconds, not milliseconds.
</ParamField>

<ParamField query="end" type="integer | string">
  Exclusive end time. Uses the same formats as `start` and defaults to the current time when omitted.

  Must be later than `start`, and `end - start` must not exceed 31 days.
</ParamField>

<ParamField query="model" type="string">
  Model names; all models are included when omitted. Separate multiple models with commas, up to `50` models.

  Exact, case-insensitive matching. Wildcards are not supported.

  Example: `gpt-5.6-luna,sora-2`
</ParamField>

<ParamField query="group_by" type="string" default="none">
  Grouping options:

  * `none`: totals only; `items` is an empty array
  * `model`: group by model
  * `date`: group by calendar day
  * `model,date`: group by both model and calendar day
</ParamField>

<ParamField query="tz" type="string" default="Asia/Shanghai">
  IANA timezone name; defaults to `Asia/Shanghai`.

  Only affects calendar-day boundaries when `group_by` includes `date`; it does not change the query start or end instants. RFC3339 times are still parsed using their own timezone offsets.
</ParamField>

<ParamField query="scope" type="string" default="key">
  Query scope:

  * `key`: only the current API Key (default)
  * `account`: all API Keys belonging to the account of the current API Key
</ParamField>

<Note>
  The query interval is `[start, end)`: inclusive start, exclusive end. Using the same boundary as one query’s `end` and the next query’s `start` avoids counting that boundary twice.

  When building URLs manually, encode the `+` in RFC3339 as `%2B`. The examples use cURL `--data-urlencode`, Python `params`, and JavaScript `URLSearchParams` to handle encoding automatically.
</Note>

## Request examples

### Total spending for the current API Key over the last 24 hours

Omit query parameters to use the default time range, scope, and grouping:

```bash theme={null}
curl 'https://api.apimart.ai/v1/usage' \
  --header 'Authorization: Bearer <token>'
```

### Daily spending for a specified model across the account

Query spending from September 11 to September 18, 2026, in Beijing time, excluding September 18:

```bash theme={null}
curl --get 'https://api.apimart.ai/v1/usage' \
  --header 'Authorization: Bearer <token>' \
  --data-urlencode 'start=1789056000' \
  --data-urlencode 'end=1789660800' \
  --data-urlencode 'model=gpt-5.6-luna' \
  --data-urlencode 'group_by=date' \
  --data-urlencode 'scope=account' \
  --data-urlencode 'tz=Asia/Shanghai'
```

### Group by model and calendar day

```bash theme={null}
curl --get 'https://api.apimart.ai/v1/usage' \
  --header 'Authorization: Bearer <token>' \
  --data-urlencode 'start=2026-09-01T00:00:00+08:00' \
  --data-urlencode 'end=2026-09-08T00:00:00+08:00' \
  --data-urlencode 'model=gpt-5.6-luna,sora-2' \
  --data-urlencode 'group_by=model,date' \
  --data-urlencode 'tz=Asia/Shanghai'
```

With this grouping, each `items` entry contains both `model` and `date`.

## Response fields

<ResponseField name="success" type="boolean">
  Whether the query succeeded: `true` on success, `false` for usage-query errors.
</ResponseField>

<ResponseField name="data" type="object">
  On success, contains the query range, totals, and grouped details.

  <Expandable title="data properties">
    <ResponseField name="scope" type="string">
      Query scope: `key` or `account`.
    </ResponseField>

    <ResponseField name="start" type="integer">
      Actual query start time as a Unix timestamp in seconds, inclusive.
    </ResponseField>

    <ResponseField name="end" type="integer">
      Actual query end time as a Unix timestamp in seconds, exclusive.
    </ResponseField>

    <ResponseField name="tz" type="string">
      Timezone used for calendar-day grouping.
    </ResponseField>

    <ResponseField name="group_by" type="string">
      Grouping: `none`, `model`, `date`, or `model,date`.
    </ResponseField>

    <ResponseField name="total" type="object">
      Totals within the query range; see the statistics below.
    </ResponseField>

    <ResponseField name="items" type="object[]">
      Grouped details, sorted by `amount_usd` in descending order. Empty when `group_by=none`. Each entry includes the statistics below.

      * Entries include `model` when `group_by` includes `model`
      * Entries include `date` when `group_by` includes `date`, formatted as `YYYY-MM-DD` with calendar days determined by `tz`
    </ResponseField>
  </Expandable>
</ResponseField>

`data.total` and `data.items[]` share these statistics:

| Field               | Type    | Description                                                                               |
| ------------------- | ------- | ----------------------------------------------------------------------------------------- |
| `amount_usd`        | number  | USD amount, rounded to 6 decimal places                                                   |
| `credits`           | number  | Platform credits, equal to `amount_usd × 10`, matching the units displayed on the website |
| `requests`          | integer | Number of successfully billed calls                                                       |
| `prompt_tokens`     | integer | Input tokens; usually 0 for image/video models billed per request or per second           |
| `completion_tokens` | integer | Output tokens; usually 0 for image/video models billed per request or per second          |

<ResponseField name="error" type="object">
  Returned for usage-query errors, with `code`, `message`, and `type`; `type` is `usage_query_error`. Authentication errors with HTTP 401 / 403 are returned by the authentication layer.
</ResponseField>

## Rate limits and caching

* Up to `60` queries per API Key per minute; global API rate limits also apply
* Results with identical parameters are cached for `60` seconds; the `X-Usage-Cache` response header is `hit` or `miss`
* Usage records are typically available within 1 second, but the last minute of data may be incomplete, with additional cache delay
* Poll no more frequently than once a minute; do not use this endpoint as a real-time billing notification

## Accounting rules

* Uses successfully billed call records, the same data as the website dashboard
* Failed calls and tasks refunded after failure are excluded; no manual offset is needed. Partially successful image batches are billed for the images actually delivered
* Spending is attributed to the billing posting time. Asynchronous image/video tasks are posted at completion, not submission; tasks crossing midnight belong to the completion day
* Deleting and recreating an API Key creates a new Key; querying `scope=key` with the new Key does not include the old Key’s history
* Manual balance adjustments are not call spending and are excluded
* Data after April 27, 2026, is available

## Error handling

| HTTP status | `error.code`                    | Description                                                       |
| ----------- | ------------------------------- | ----------------------------------------------------------------- |
| 400         | `invalid_start` / `invalid_end` | Invalid start or end time format                                  |
| 400         | `invalid_range`                 | `end` is not later than `start`                                   |
| 400         | `range_too_large`               | Range exceeds 31 days; split it into multiple queries             |
| 400         | `invalid_tz`                    | Unknown IANA timezone                                             |
| 400         | `invalid_group_by`              | Invalid grouping option                                           |
| 400         | `invalid_scope`                 | Invalid query scope                                               |
| 400         | `too_many_models`               | More than 50 models                                               |
| 401 / 403   | —                               | Invalid or expired API Key, IP not allowed, or disabled account   |
| 429         | —                               | Exceeds 60 queries per Key per minute, or a global API rate limit |
| 503         | `usage_unavailable`             | Usage data is temporarily unavailable; retry later                |

<Warning>
  A `503 usage_unavailable` response returns no amounts. It means the query is unavailable, not that spending is zero. Do not convert failed responses into zero amounts or overwrite a previous successful result.
</Warning>

## Comparison with other endpoints

| Endpoint                          | Capability                                                                |
| --------------------------------- | ------------------------------------------------------------------------- |
| `GET /v1/dashboard/billing/usage` | Cumulative spending only; no model or time filters                        |
| `POST /v1/logs/export`            | Asynchronous export of call details (CSV / XLSX); aggregate them yourself |
| `GET /v1/usage`                   | Query by time and model; directly returns totals and grouped spending     |

For remaining quota, use [Query Token Balance](/en/api-reference/account/token-balance) or [Query User Balance](/en/api-reference/account/user-balance).
