Reference

Usage & credits

Per-workflow usage aggregates and the paginated credit ledger — reconcile spend and monitor the prepaid balance.

Obsidian bills verification work against a prepaid credit balance. These two /v3 read endpoints expose spend two ways: a rolled-up view grouped by workflow, and the raw ledger of every individual debit and top-up. Use the aggregate for dashboards and the ledger for line-by-line reconciliation.

Info

Both endpoints are on the /v3 surface and authenticate with x-api-key. Errors carry { "detail": "…" }. See Core concepts → Credits for the billing model.

Workflow usage

GET /v3/usage/workflows/
x-api-key: <tenant_api_key>

Aggregated credit spend and billed-entry counts per workflow. The server sums every metered debit (negative ledger rows tied to a session) and groups by the session's workflow name. Sessions not linked to a named workflow roll up under "-". There are no query parameters — the response covers the tenant's full history.

curl -s https://acme.thirdfactor.ai/v3/usage/workflows/ \
  -H "x-api-key: $OBSIDIAN_API_KEY"
const resp = await fetch("https://acme.thirdfactor.ai/v3/usage/workflows/", {
  headers: { "x-api-key": process.env.OBSIDIAN_API_KEY },
});
const { usage } = await resp.json();
const total = usage.reduce((sum, row) => sum + row.credits, 0);
import requests

resp = requests.get(
    "https://acme.thirdfactor.ai/v3/usage/workflows/",
    headers={"x-api-key": OBSIDIAN_API_KEY},
)
usage = resp.json()["usage"]
total = sum(row["credits"] for row in usage)

Response Example

{
  "usage": [
    { "workflow": "Age check only", "credits": 210.0, "entries": 210 },
    { "workflow": "Standard onboarding", "credits": 1240.0, "entries": 310 },
    { "workflow": "-", "credits": 12.0, "entries": 4 }
  ]
}
usagearray
One entry per workflow, sorted by workflow name.
workflowstring

Workflow name. "-" groups sessions with no linked/named workflow.

creditsnumber

Total credits consumed by that workflow (rounded to 4 decimals). Always a positive magnitude — the underlying debits are negative ledger deltas.

entriesinteger

Number of billed session-debit rows attributed to the workflow.

Errors

StatusCodeWhen
401invalid API keyMissing or wrong x-api-key.
429rate-limitedOver the kyc_v3_read bucket.

Credit ledger

GET /v3/credits/ledger/
x-api-key: <tenant_api_key>

The raw, paginated ledger of every credit movement — session debits, per-module debits, tool usage, refunds, top-ups, and manual adjustments — newest first.

sincestringquery

Start date (inclusive), YYYY-MM-DD. A malformed value returns 400 { "detail": "since must be YYYY-MM-DD." }.

untilstringquery

End date (inclusive), YYYY-MM-DD. A malformed value returns 400 { "detail": "until must be YYYY-MM-DD." }.

pageintegerqueryDefault: 1

Page number. Values below 1 are clamped to 1.

page_sizeintegerqueryDefault: 100

Rows per page, clamped to 1..500.

curl -s "https://acme.thirdfactor.ai/v3/credits/ledger/?since=2026-07-01&until=2026-07-31&page=1&page_size=100" \
  -H "x-api-key: $OBSIDIAN_API_KEY"
const params = new URLSearchParams({
  since: "2026-07-01",
  until: "2026-07-31",
  page: "1",
  page_size: "100",
});
const resp = await fetch(
  `https://acme.thirdfactor.ai/v3/credits/ledger/?${params}`,
  { headers: { "x-api-key": process.env.OBSIDIAN_API_KEY } },
);
const ledger = await resp.json();
resp = requests.get(
    "https://acme.thirdfactor.ai/v3/credits/ledger/",
    headers={"x-api-key": OBSIDIAN_API_KEY},
    params={"since": "2026-07-01", "until": "2026-07-31", "page": 1, "page_size": 100},
)
ledger = resp.json()

Response Example

{
  "count": 128,
  "page": 1,
  "page_size": 100,
  "results": [
    {
      "id": "3f7a1c9e-2b4d-4e6f-8a1b-0c2d3e4f5a6b",
      "delta": "-3.0000",
      "reason": "session_create",
      "feature": "SESSION_CREATE",
      "session_id": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
      "application_id": "",
      "created_at": "2026-07-03T08:00:00Z"
    },
    {
      "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "delta": "-1.0000",
      "reason": "kyc_module",
      "feature": "AML_SCREENING",
      "session_id": "6f0e2b7a-1c2d-4e5f-8a9b-0c1d2e3f4a5b",
      "application_id": "",
      "created_at": "2026-07-03T08:12:00Z"
    },
    {
      "id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
      "delta": "5000.0000",
      "reason": "topup",
      "feature": "",
      "session_id": "",
      "application_id": "",
      "created_at": "2026-07-01T00:00:00Z"
    }
  ]
}
countinteger
Total ledger rows matching the date filter (across all pages).
pageinteger
Current page (echoed back after clamping).
page_sizeinteger
Rows per page (echoed back after clamping to 1..500).
resultsarray
The ledger rows for this page, newest first.

Each row in results:

idstring
Ledger row UUID.
deltastring

Signed credit change as a decimal string (e.g. "-3.0000" for a debit, "5000.0000" for a top-up). Parse it as a number before arithmetic.

reasonstring

Movement type: session_create, kyc_session (legacy per-session lump), kyc_module (per-feature metered), tool_usage, refund, topup, or adjust.

featurestring

The chargeable feature key for usage rows (e.g. AML_SCREENING, FACE_SEARCH). Blank on non-usage rows like topup/adjust.

session_idstring
The KYC session this row bills, when applicable (else empty string).
application_idstring
The application this row bills, when applicable (else empty string).
created_atstring
ISO 8601 timestamp of the movement.

Errors

StatusCodeWhen
400since must be YYYY-MM-DD. / until must be YYYY-MM-DD.Malformed date filter.
401invalid API keyMissing or wrong x-api-key.
429rate-limitedOver the kyc_v3_read bucket.

Reconciliation example

Page through the ledger for a month and sum debits by feature to reconcile against your own invoice or internal usage counters.

async function reconcile(since, until) {
  const byFeature = {};
  let page = 1;
  for (;;) {
    const params = new URLSearchParams({ since, until, page: String(page), page_size: "500" });
    const resp = await fetch(
      `https://acme.thirdfactor.ai/v3/credits/ledger/?${params}`,
      { headers: { "x-api-key": process.env.OBSIDIAN_API_KEY } },
    );
    const { results, count, page_size } = await resp.json();
    for (const row of results) {
      const delta = parseFloat(row.delta);
      if (delta < 0) {
        const key = row.feature || row.reason;
        byFeature[key] = (byFeature[key] || 0) + -delta;
      }
    }
    if (page * page_size >= count) break;
    page += 1;
  }
  return byFeature;
}

console.log(await reconcile("2026-07-01", "2026-07-31"));
import requests

def reconcile(since, until):
    by_feature, page = {}, 1
    while True:
        resp = requests.get(
            "https://acme.thirdfactor.ai/v3/credits/ledger/",
            headers={"x-api-key": OBSIDIAN_API_KEY},
            params={"since": since, "until": until, "page": page, "page_size": 500},
        )
        body = resp.json()
        for row in body["results"]:
            delta = float(row["delta"])
            if delta < 0:
                key = row["feature"] or row["reason"]
                by_feature[key] = by_feature.get(key, 0.0) + -delta
        if page * body["page_size"] >= body["count"]:
            break
        page += 1
    return by_feature

print(reconcile("2026-07-01", "2026-07-31"))

Aggregate vs. ledger

GET /v3/usage/workflows/GET /v3/credits/ledger/
GranularityRolled up per workflowOne row per movement
Includes top-ups / refunds❌ — debits only✅ — all movements
Date filtering❌ — full history✅ — since / until
Pagination❌ — small, returned whole✅ — page / page_size
Best forDashboards, spend-by-productLine-by-line reconciliation, audits

Note

Creating a session debits the SESSION_CREATE rate; a 402 insufficient_credits response on session create means the balance is exhausted. Top up before the balance runs out — monitor it with the ledger's running topup/adjust rows. See Core concepts → Credits.

Common pitfalls

Warning

Treating delta as a number. It is a decimal string ("-3.0000"). parseFloat / float() it before summing, or your totals silently concatenate or throw.

Warning

Assuming the aggregate matches the ledger sum. usage/workflows counts only session-linked debits; it excludes top-ups, refunds, and adjustments. To tie out the exact balance movement, sum the ledger, not the aggregate.

FAQ