Skip to content

API reference/account

Read the credit ledger

GET/api/v1/account/ledger

authentication
Secret API key as a bearer token
billing
Free.

Every credit movement on the account, newest first: lookups debited, monthly grants, purchased packs and plan deposits. Each row carries the signed delta, the balance_after it left, the reason, an optional reference (a Stripe session or invoice id for purchases), and via, the label of the key, widget, bulk run or monitor that spent it. Each row spent through a secret key also carries that key's api_token_id, which is what tells two keys of the same name apart and what a rename does not move; it is null for the dashboard, for bulk runs and monitors, and for grants and purchases. An ip_lookup row is one credit paying for five answered IP lookups: the lookup that opens a block writes it, and the four after it write nothing (rows written before IP lookups were billed in blocks stand for one lookup each). Filter with since and until (ISO 8601 dates), reason, via and api_token_id; page with page and per_page (up to 200). The dashboard's Usage page offers the same rows as a CSV download.

Parameters

name in type description
sincequerystringOnly rows on or after this date.
untilquerystringOnly rows on or before this date.
reasonquerystringOnly rows with this reason, e.g. email_lookup, ip_lookup, monthly_grant, purchase, subscription, plan_credits_expired.
viaquerystringOnly rows spent through this label, e.g. "api · Production".
api_token_idqueryintegerOnly rows spent by this secret key. An id belonging to another account matches no rows, because the ledger is scoped to the caller first.
pagequeryinteger
per_pagequeryintegerDefault: 50.

Example request

curl https://spaw.co/api/v1/account/ledger \
  -H "Authorization: Bearer sk_live_…"
const response = await fetch('https://spaw.co/api/v1/account/ledger', {
  method: 'GET',
  headers: {
    'Authorization': 'Bearer sk_live_…',
  },
});
const result = await response.json();
import requests

response = requests.get(
    'https://spaw.co/api/v1/account/ledger',
    headers={'Authorization': 'Bearer sk_live_…'},
)
result = response.json()
$ch = curl_init('https://spaw.co/api/v1/account/ledger');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ['Authorization: Bearer sk_live_…'],
]);
$result = json_decode(curl_exec($ch), true);
from spaw import Client

client = Client('sk_live_…')
result = client.ledger()
import Spaw from 'spaw';

const spaw = new Spaw({ apiKey: 'sk_live_…' });
const result = await spaw.ledger();
use Spaw\Client;

$spaw = new Client('sk_live_…');
$result = $spaw->ledger();

This endpoint has no console on its page. It answers with records from your own account, often by an id only you have, rather than about a value you can type here. The dashboard lists the same rows.

Responses

200One page of ledger rows.

{
    "success": true,
    "data": [
        {
            "id": 9812,
            "delta": -1,
            "balance_after": 99,
            "lookups": 5,
            "reason": "ip_lookup",
            "reference": null,
            "via": "api · Production",
            "api_token_id": 41,
            "created_at": "2026-09-04T10:12:44+00:00"
        },
        {
            "id": 9801,
            "delta": 100,
            "balance_after": 100,
            "lookups": 0,
            "reason": "monthly_grant",
            "reference": "2026-09",
            "via": null,
            "api_token_id": null,
            "created_at": "2026-09-01T00:00:03+00:00"
        }
    ],
    "meta": {
        "page": 1,
        "per_page": 50,
        "total": 2,
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

401The key is missing, malformed, or revoked.

{
    "success": false,
    "error": {
        "code": "UNAUTHENTICATED",
        "message": "Provide a valid API key as a bearer token.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

422The request body could not be validated; `error.errors` lists the fields.

{
    "success": false,
    "error": {
        "code": "VALIDATION_FAILED",
        "message": "The email field is required.",
        "errors": {
            "email": [
                "The email field is required."
            ]
        },
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

429Over 5 requests per second for the key. Retry after the limit resets.

{
    "success": false,
    "error": {
        "code": "RATE_LIMITED",
        "message": "Too many requests. Retry after the limit resets.",
        "request_id": "req_01m1kgdm4xngzmbmff68g94w0c"
    }
}

Error codes

Failures answer { success: false, error: { code, message, request_id } }. Each code has its own page.

markdown version·openapi.json