---
title: "Credit transactions"
method: GET
path: "/v1/usage/credits/transactions"
tags: ["Account"]
---

# Credit transactions

`GET /v1/usage/credits/transactions`

## Request

- Base URL: `https://api.kawaa.com`
- URL: `https://api.kawaa.com/v1/usage/credits/transactions`
- Auth: one of:
  - API key in header `X-Api-Key`
  - HTTP bearer
  - OAuth 2 (authorization code, authorization URL `https://api.kawaa.com/oauth/authorize`, token URL `https://api.kawaa.com/oauth/token`, scopes: `analytics:read`)

The account's credit ledger, newest first: the charges and refunds on the balance, each with the balance it left. After a failure that carries `retry_may_charge_again` it shows whether a debit of that amount landed around that moment, which `GET /v1/usage` (the balance alone) cannot. A row carries no request id and no address, so that settles the question only when nothing else on the account was spending at the same time; on a route that accepts one, an `Idempotency-Key` is the reliable answer. Entries before 2 September 2026 show usage and refunds only: credits added before then, from a plan or a credit pack, have no row, so `type=purchase` is empty for that period even for an account that bought credits then. From that date every paid-plan allowance and credit-pack purchase has a `purchase` row; the one-time Free signup grant is the exception, credited to the balance directly with no row.

## Query parameters

- `limit` integer
- `offset` integer
- `type` 'purchase' | 'usage' | 'refund' | 'bonus' | 'rollover' | 'adjustment' | 'transfer'
- `cursor` string

## Response `200`

One page of the ledger.

- object
  - `success` unknown, required
  - `data` object, required
    - `transactions` object[], required
      - `id` string, required: The same value as `transaction_id`.
      - `transaction_id` string, required
      - `type` 'purchase' | 'usage' | 'refund' | 'bonus' | 'rollover' | 'adjustment' | 'transfer', required
      - `amount` number, required: Negative for a debit, positive for credit coming back. Fractional: a cached verification is -0.5.
      - `balance_after` number, required: The balance this row left.
      - `description` string, required: What the credits were for, written by the feature that moved them. A generic label for the row's type when `detail_withheld` is true.
      - `reference_id` string, nullable, required
      - `detail_withheld` unknown
      - `created_at` string, date-time, required
    - `total` integer, required: Rows matching the request, across every page.
    - `limit` integer, required
    - `offset` integer, required: The `offset` the request sent; 0 for a page read from `cursor`.
    - `has_more` boolean, required: True when another row exists after this page.
    - `next_cursor` string, nullable, required: Pass it back as `cursor` to read the page after this one; null on the last page. Unlike `offset`, it reaches past the 10000th row.

## Other responses

- `400`: The request is malformed, or names a field this endpoint does not accept. Retrying it unchanged will fail identically.
- `401`: No credential, or one that is not accepted. `code` distinguishes `API_KEY_SUSPENDED` (subscription ended; restartable) from `API_KEY_REVOKED`, `SESSION_EXPIRED` and `OAUTH_TOKEN_EXPIRED` (a connected app's one-hour access token lapsed: refresh it and retry, or sign in again if no refresh token was issued or the refresh is refused).
- `403`: Refused, and **the code says which of three problems it is**, and they need different remedies: - `INSUFFICIENT_SCOPE`: the credential is valid but not scoped for this route. With an API key, issue one that carries the scope named in the message. With an OAuth access token, connect the app again asking for that scope as well as the ones it has, unless the message says a connected app can never be given it; then use an API key with every scope the message names, or have the account owner do it in the dashboard. - `PLAN_UPGRADE_REQUIRED`: the account's plan does not include the feature. **No key of any scope reaches it**, so issuing another is wasted work; the remedy is a plan change. Returned by the verification routes for `options.enrich`, and by the finder, the monitors, the audit log, auto-clean and the white-label routes. - `PLAN_LIMIT_EXCEEDED`: the plan includes the feature and the account is at its ceiling (webhook endpoints, team seats, integrations, auto-clean lists). Delete one or upgrade. - `FORBIDDEN`: refused for a reason none of the above covers. `ErrorCode` is an open set, so branch on the code with a default rather than exhaustively.
- `429`: Too many requests. When the server knows how long to wait, `Retry-After` says so and `error.retry_after_seconds` carries the same number; the `X-RateLimit-*` headers say where you stand.
- `default`: Any status this operation does not list. Kawaa's own errors carry the error envelope. The gateway in front of the API can answer any operation with its own bare body (`GatewayError`): a `503` after about 29 seconds, when the request may still have run and been charged, a `413` for a body over about 6 MB, or a `400` for a path with malformed percent-encoding.

---

[API](https://skmtc.dev/kawaa/apis/kawaa-api.md) · [All operations](https://skmtc.dev/kawaa/apis/kawaa-api/llms.txt) · [OpenAPI document](https://skmtc.dev/kawaa/apis/kawaa-api/revisions/d27c9c33f6ba?raw)
