List the workspace's credit history
#!/usr/bin/env bashset -euo pipefailAPI=https://api.speccapture.comAUTH="Authorization: Bearer $SPECCAPTURE_API_KEY"
page=$(curl -sS --fail-with-body "$API/v1/credits/history?limit=20" \ -H "$AUTH") || { echo "$page" >&2; exit 1; }
jq -r '.data[] as $entry | "\($entry.occurredAt) \($entry.type) of \($entry.credits) credits: \($entry.availableChange) available, \($entry.heldChange) held"' <<<"$page"
if [[ "$(jq -r '.nextCursor' <<<"$page")" != null ]]; then jq -r '"Older entries: send cursor=\(.nextCursor)"' <<<"$page"fiusing System.Text.Json;
using HttpClient http = new() { BaseAddress = new Uri("https://api.speccapture.com") };http.DefaultRequestHeaders.Authorization = new("Bearer", Environment.GetEnvironmentVariable("SPECCAPTURE_API_KEY"));
JsonElement page = await Json(await Call(HttpMethod.Get, "/v1/credits/history?limit=20"));
foreach (JsonElement entry in page.GetProperty("data").EnumerateArray()) { Console.WriteLine($"{entry.GetProperty("occurredAt")} {entry.GetProperty("type")} of {entry.GetProperty("credits")} credits: {entry.GetProperty("availableChange")} available, {entry.GetProperty("heldChange")} held");}
if (page.GetProperty("nextCursor").ValueKind != JsonValueKind.Null) { Console.WriteLine($"Older entries: send cursor={page.GetProperty("nextCursor")}");}
// Sends a request; stops with the problem's code and detail if the API refuses it.async Task<HttpResponseMessage> Call(HttpMethod method, string path, HttpContent? body = null, string? idempotencyKey = null) { using HttpRequestMessage request = new(method, path) { Content = body }; if (idempotencyKey is not null) request.Headers.Add("Idempotency-Key", idempotencyKey); HttpResponseMessage response = await http.SendAsync(request); if (!response.IsSuccessStatusCode) { JsonElement problem = await Json(response); string? detail = (problem.TryGetProperty("detail", out JsonElement text) ? text : problem.GetProperty("title")).GetString(); Console.Error.WriteLine($"{(int)response.StatusCode} {problem.GetProperty("code")}: {detail}"); Environment.Exit(1); } return response;}
// Reads an answer as JSON.static async Task<JsonElement> Json(HttpResponseMessage response) => JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;import osimport sys
import requests
API = "https://api.speccapture.com"session = requests.Session()session.headers["Authorization"] = f"Bearer {os.environ['SPECCAPTURE_API_KEY']}"
def call(method, path, **kwargs): """Send a request; stop with the problem's code and detail if the API refuses it.""" response = session.request(method, API + path, **kwargs) if not response.ok: problem = response.json() sys.exit(f"{response.status_code} {problem['code']}: {problem.get('detail', problem['title'])}") return response
page = call("GET", "/v1/credits/history?limit=20").json()
for entry in page["data"]: print(f"{entry['occurredAt']} {entry['type']} of {entry['credits']} credits: {entry['availableChange']} available, {entry['heldChange']} held")
if page["nextCursor"] is not None: print(f"Older entries: send cursor={page['nextCursor']}")const API = "https://api.speccapture.com";const headers = { Authorization: `Bearer ${process.env.SPECCAPTURE_API_KEY}` };
const page = await call("GET", "/v1/credits/history?limit=20").then((response) => response.json());
for (const entry of page.data) { console.log(`${entry.occurredAt} ${entry.type} of ${entry.credits} credits: ${entry.availableChange} available, ${entry.heldChange} held`);}
if (page.nextCursor !== null) { console.log(`Older entries: send cursor=${page.nextCursor}`);}
// Sends a request; stops with the problem's code and detail if the API refuses it.async function call(method, path, init = {}) { const response = await fetch(API + path, { ...init, method, headers: { ...headers, ...init.headers } }); if (!response.ok) { const problem = await response.json(); console.error(`${response.status} ${problem.code}: ${problem.detail ?? problem.title}`); process.exit(1); } return response;}Every change to the workspace’s credits, newest first: each grant (grantKind trial, plan, pack or addedBySpecCapture, with when it expires and what of it remains), each job’s reservation, each sheet’s spend, each refund, and each expiry of credits left unspent. Each entry says how many credits it moved and what it did to the credits available and held (availableChange, heldChange), so the whole history adds up to GET /v1/account’s credits; a job’s entries name the job, its file and the sheet. Page with limit and cursor (the previous page’s nextCursor), narrow with createdAfter and createdBefore (when an entry happened) and type.
Authorizations
Section titled “Authorizations”Parameters
Section titled “Parameters”Query Parameters
Section titled “Query Parameters”How many items the page holds: 1 to 200, 50 when left out.
The previous page’s nextCursor, exactly as it came: the list continues just after that page’s last item, so items made or removed meanwhile never shift a page. Leave it out for the first page.
Only items that happened strictly after this date and time (ISO 8601, such as 2026-10-14T09:30:00Z; without an offset it is UTC).
Only items that happened strictly before this date and time (ISO 8601, such as 2026-10-14T09:30:00Z; without an offset it is UTC).
Only entries of this type.
Responses
Section titled “Responses”OK
object
object
Bad Request: a problem with a stable code. See Errors for every code and what to do about it.
Every error: a problem with a stable code. https://speccapture.com/docs/api/errors lists every code.
object
A link to the code’s entry in the docs.
What went wrong, as a stable code to branch on: a published code never changes meaning.
This request’s trace: quote it when asking for help.