Billing
Reading your own balance and usage — and one honest limit on where from.
Every amount is an integer
1999 with currency: "USD" is $19.99. The exponent is not assumed: it travels with the amount as minor_exponent (2 for USD/EUR/GBP/AED, 0 for IRR/IRT/TOMAN, which have no subunit in practice), so 500 with minor_exponent: 0 is 500 units whole, not 5.00. Divide by 10 ** minor_exponent yourself; never trust a pre-formatted string for a calculation.{"amount_minor": 1999, "currency": "USD", "minor_exponent": 2, "display": "19.99"}Where these endpoints live
/api/accounts/billing/* — not with an API key, and not under /v1. There is currently no way to read balance or usage with an sk-larsa-… key. If you need this automated outside a browser, the working approach today is to drive the console session (submit the sign-in form, keep the cookie jar) rather than expecting your API key to open the door.The endpoints
/api/accounts/billing/balance{
"org_id": "org_9k2m...",
"currency": "USD",
"balance": {"amount_minor": 4231, "currency": "USD", "minor_exponent": 2, "display": "42.31"},
"month_usage": {"amount_minor": 1269, "currency": "USD", "minor_exponent": 2, "display": "12.69"},
"monthly_cap": null,
"tier": "paid",
"blocked": false,
"blocked_reason": ""
}/api/accounts/billing/transactions| Parameter | Type | Description |
|---|---|---|
| limit Optional | integer | 1 to 200. Default: 50 |
| before_id Optional | integer | Keyset pagination, not an offset — see Pagination below. |
{
"object": "list",
"org_id": "org_9k2m...",
"data": [
{"id": 4821, "kind": "usage", "amount_minor": -37, "currency": "USD",
"balance_after_minor": 4231, "ref": "req_a1b2", "description": "larsa-general",
"created_at": "2026-08-26T09:14:02Z", "created_by": null}
],
"next_before_id": 4820
}/api/accounts/billing/usage| Parameter | Type | Description |
|---|---|---|
| days Optional | integer | 1 to 366. Both the per-model totals and the daily series cover this same window, so they always agree with each other. Default: 30 |
{
"org_id": "org_9k2m...", "window_days": 30, "currency": "USD",
"total": {"amount_minor": 1269, "currency": "USD", "minor_exponent": 2, "display": "12.69"},
"data": [
{"model": "larsa-general", "prompt_tokens": 402113, "completion_tokens": 88221,
"requests": 512, "cost": {"amount_minor": 890, "currency": "USD", "minor_exponent": 2, "display": "8.90"}}
],
"daily": [
{"day": "2026-08-25", "requests": 41, "tokens": 19332,
"cost": {"amount_minor": 61, "currency": "USD", "minor_exponent": 2, "display": "0.61"}}
]
}/api/accounts/billing/statementsEvery calendar month this org has a statement for, newest first — a month that only saw a top-up and no API calls still appears, because a customer looking for it should find it, not a 404.
/api/accounts/billing/statements/{period}| Parameter | Type | Description |
|---|---|---|
| period Required | string | YYYY-MM, e.g. 2026-08. |
Nothing is stored when a statement is rendered — it is usage_event (what was consumed) plus ledger (what was paid) grouped by month, at read time. Re-rendering last March in two years produces the same document, and a correction posted today shows up in the month it was posted, never rewriting a document already issued.
Pagination
/billing/transactions uses keyset pagination on the ledger's own id, not OFFSET. The ledger only ever grows at the head, so an offset would shift every page under a reader the moment a new usage row lands mid-scroll — rows would repeat or vanish. Pass the previous page's next_before_id as before_id to fetch the next one; a null means you have reached the end.
Calling it with a session
Every sample below sends the console's session cookie rather than a Bearer token — sign in through the console's own form first and keep the cookie jar your HTTP client already gives you.
# sign in once, keep the cookies, then read balance from the same origin
curl -c cookies.txt -s https://api.console.larsa.larsima.com/api/accounts/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "you@example.com", "password": "…"}' > /dev/null
curl -b cookies.txt https://api.console.larsa.larsima.com/api/accounts/billing/balanceimport requests
session = requests.Session()
session.post("https://api.console.larsa.larsima.com/api/accounts/auth/login",
json={"email": "you@example.com", "password": "…"}).raise_for_status()
balance = session.get(
"https://api.console.larsa.larsima.com/api/accounts/billing/balance").json()// A CookieJar-aware fetch (e.g. `tough-cookie` + `fetch-cookie`) is the practical
// way to do this outside a real browser.
import fetchCookie from "fetch-cookie";
const fetchWithCookies = fetchCookie(fetch);
await fetchWithCookies("https://api.console.larsa.larsima.com/api/accounts/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "you@example.com", password: "…" }),
});
const balance = await fetchWithCookies(
"https://api.console.larsa.larsima.com/api/accounts/billing/balance").then((r) => r.json());import fetchCookie from "fetch-cookie";
const fetchWithCookies = fetchCookie(fetch);
interface Balance {
balance: { amount_minor: number; currency: string; display: string };
blocked: boolean;
}
await fetchWithCookies("https://api.console.larsa.larsima.com/api/accounts/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email: "you@example.com", password: "…" }),
});
const balance: Balance = await fetchWithCookies(
"https://api.console.larsa.larsima.com/api/accounts/billing/balance").then((r) => r.json());package main
import (
"bytes"
"net/http"
"net/http/cookiejar"
)
func main() {
jar, _ := cookiejar.New(nil)
client := &http.Client{Jar: jar}
client.Post("https://api.console.larsa.larsima.com/api/accounts/auth/login",
"application/json",
bytes.NewReader([]byte(`{"email":"you@example.com","password":"…"}`)))
resp, _ := client.Get("https://api.console.larsa.larsima.com/api/accounts/billing/balance")
defer resp.Body.Close()
}fn main() -> Result<(), Box<dyn std::error::Error>> {
// reqwest's `cookie_store(true)` keeps the session cookie across calls.
let client = reqwest::blocking::Client::builder()
.cookie_store(true)
.build()?;
client.post("https://api.console.larsa.larsima.com/api/accounts/auth/login")
.json(&serde_json::json!({"email": "you@example.com", "password": "…"}))
.send()?;
let balance: serde_json::Value = client
.get("https://api.console.larsa.larsima.com/api/accounts/billing/balance")
.send()?
.json()?;
Ok(())
}import java.net.URI;
import java.net.http.*;
import java.net.CookieManager;
public class Balance {
public static void main(String[] args) throws Exception {
CookieManager cookies = new CookieManager();
HttpClient client = HttpClient.newBuilder().cookieHandler(cookies).build();
client.send(HttpRequest.newBuilder()
.uri(URI.create("https://api.console.larsa.larsima.com/api/accounts/auth/login"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"email\":\"you@example.com\",\"password\":\"…\"}"))
.build(), HttpResponse.BodyHandlers.discarding());
HttpResponse<String> resp = client.send(HttpRequest.newBuilder()
.uri(URI.create("https://api.console.larsa.larsima.com/api/accounts/billing/balance"))
.build(), HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());
}
}using System.Net;
using System.Text;
var handler = new HttpClientHandler { CookieContainer = new CookieContainer() };
using var client = new HttpClient(handler);
await client.PostAsync("https://api.console.larsa.larsima.com/api/accounts/auth/login",
new StringContent("{\"email\":\"you@example.com\",\"password\":\"…\"}",
Encoding.UTF8, "application/json"));
var resp = await client.GetAsync(
"https://api.console.larsa.larsima.com/api/accounts/billing/balance");
Console.WriteLine(await resp.Content.ReadAsStringAsync());<?php
$cookieFile = tempnam(sys_get_temp_dir(), "larsa");
function larsaRequest(string $url, string $cookieFile, ?array $body = null): string {
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_COOKIEJAR => $cookieFile,
CURLOPT_COOKIEFILE => $cookieFile,
CURLOPT_RETURNTRANSFER => true,
]);
if ($body !== null) {
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($body));
curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/json"]);
}
return curl_exec($ch);
}
larsaRequest("https://api.console.larsa.larsima.com/api/accounts/auth/login", $cookieFile,
["email" => "you@example.com", "password" => "…"]);
$balance = larsaRequest(
"https://api.console.larsa.larsima.com/api/accounts/billing/balance", $cookieFile);require "net/http"
require "json"
require "uri"
login_uri = URI("https://api.console.larsa.larsima.com/api/accounts/auth/login")
login_req = Net::HTTP::Post.new(login_uri, "Content-Type" => "application/json")
login_req.body = { email: "you@example.com", password: "…" }.to_json
login_res = Net::HTTP.start(login_uri.host, login_uri.port, use_ssl: true) { |h| h.request(login_req) }
cookie = login_res["set-cookie"]
balance_uri = URI("https://api.console.larsa.larsima.com/api/accounts/billing/balance")
balance_req = Net::HTTP::Get.new(balance_uri, "Cookie" => cookie)
balance_res = Net::HTTP.start(balance_uri.host, balance_uri.port, use_ssl: true) { |h| h.request(balance_req) }
balance = JSON.parse(balance_res.body)