This section as Markdown: View Download

Agent Transactions

Per-instance credit ledger: every credit deduction, renewal, purchase, refund, grant, and cancel for a useragent in a single immutable feed.

Overview

Wiro keeps a complete, append-only agent transaction ledger for every UserAgent instance. Whenever credits move (the agent runtime burns them on a chat turn, a subscription renews, a Pro user buys an extra-credit pack, the user disables a paid skill mid-period and gets a refund), a row is inserted into the ledger and surfaced through POST /UserAgent/TransactionList.

The ledger is the single source of truth for "where did my credits go?" and powers the Transactions view in your dashboard.

Transaction type Source When it fires
deduct Agent runtime Per-turn LLM token charge (action: "tokens") for every chat, cron, and voice post-call turn. Negative amount.
renewal Subscription cron At each 30-day rollover when the wallet successfully covers the renewal. Positive amount = monthly credits granted.
purchase Extra-credit checkout When POST /UserAgent/CreateExtraCreditCheckout (useprepaid: true) succeeds. Positive amount = pack credits.
grant Skill toggle / tier upgrade When a skill is enabled mid-period and the credit pool grows, or when a tier upgrade lifts the monthly allocation. Positive amount: what this period gained. metadata.creditsFromRenewal is what each renewal grants.
expired Skill toggle / subscription end Two cases share the type. Mid-period: when a skill is disabled and the credit pool shrinks (action: "skill-toggle" or "skills-apply"); amount is what this period lost, and metadata.usedCreditsUsd is what came off the credit for credits already used. End-of-period: when a cancelled subscription rolls past currentperiodend and the cron revokes any remaining monthly leftover (action: "subscription"). Negative amount in both cases.
refund Server-side refund Issued by Wiro support when a previous charge is reversed. Positive amount.
adjust Skill change / Wiro support A record for Wiro support. amount is 0 on action: "skill-change-not-undone" (a failed change that could not be taken back) and action: "skill-credit-failed" (a credit that was not added to the wallet).
The ledger is append-only. There is no delete endpoint. Each row carries a stable guid that the server uses for deduplication (so a duplicate retry of the same deduct event is a no-op).

POST /UserAgent/TransactionList

Returns the ledger rows for a single useragent sorted newest-first, plus a snapshot summary of the current balance.

Parameter Type Required Description
useragentguid string Yes The useragent instance guid.
limit number No Max rows to return. Default 50, max 500.
start number No Offset for pagination. Default 0.

Authorization: the agent's owner, a team admin of the agent's team or the organization owner (plain team members are rejected with code 97). No extra header is needed.

Request

curl -X POST "https://api.wiro.ai/v1/UserAgent/TransactionList" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "useragentguid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321", "limit": 50 }'

Response

{
  "result": true,
  "errors": [],
  "total": 312,
  "summary": {
    "monthlycredits": 9000,
    "extracredits": 45000,
    "usedcredits": 1450,
    "remainingcredits": 52550,
    "renewalcredits": 9000,
    "creditperiod": "2026-05",
    "creditsyncat": 1714694410,
    "byModel": [
      { "model": "openai/gpt-5.4", "inputtokens": 1011250, "outputtokens": 241300, "cachereadtokens": 149800, "cachewritetokens": 0, "totaltokens": 1402350, "tokencost": 1443, "turncount": 288 },
      { "model": "unknown", "inputtokens": 4200, "outputtokens": 900, "cachereadtokens": 0, "cachewritetokens": 0, "totaltokens": 5100, "tokencost": 7, "turncount": 3 }
    ]
  },
  "transactions": [
    {
      "guid": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e01",
      "type": "deduct",
      "action": "tokens",
      "amount": -5,
      "balanceafter": null,
      "description": "LLM openai/gpt-5.4 (uncached in 3450, cached in 512, out 820, total 4782 tokens)",
      "sessionkey": "default",
      "agentsessionkey": "default",
      "messageguid": "5d6e7f80-9a1b-4c2d-8e3f-4a5b6c7d8e01",
      "inputtokens": 3450,
      "outputtokens": 820,
      "cachereadtokens": 512,
      "cachewritetokens": 0,
      "totaltokens": 4782,
      "tokencost": 5,
      "processedms": 4120,
      "model": "openai/gpt-5.4",
      "provider": "agent",
      "providerref": null,
      "metadata": null,
      "uuid": "ada-uuid",
      "user": {
        "uuid": "ada-uuid",
        "firstname": "Ada",
        "lastname": "Lovelace",
        "email": "ada@example.com",
        "username": "ada",
        "avatar": "https://cdn.wiro.ai/avatars/ada.webp",
        "avatarinitials": "AL"
      },
      "createdat": 1714694400
    },
    {
      "guid": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e02",
      "type": "purchase",
      "action": "small",
      "amount": 45000,
      "balanceafter": 52555,
      "description": "Extra credits — small pack",
      "sessionkey": null,
      "messageguid": null,
      "inputtokens": null,
      "outputtokens": null,
      "cachereadtokens": null,
      "cachewritetokens": null,
      "totaltokens": null,
      "tokencost": null,
      "processedms": null,
      "model": null,
      "provider": "prepaid",
      "providerref": null,
      "metadata": { "pack": "small", "priceUsd": 450 },
      "uuid": "ada-uuid",
      "user": {
        "uuid": "ada-uuid",
        "firstname": "Ada",
        "lastname": "Lovelace",
        "email": "ada@example.com",
        "username": "ada",
        "avatar": "https://cdn.wiro.ai/avatars/ada.webp",
        "avatarinitials": "AL"
      },
      "createdat": 1714692800
    },
    {
      "guid": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e03",
      "type": "renewal",
      "action": "monthly",
      "amount": 9000,
      "balanceafter": 9000,
      "description": "Subscription renewal",
      "sessionkey": null,
      "messageguid": null,
      "inputtokens": null,
      "outputtokens": null,
      "cachereadtokens": null,
      "cachewritetokens": null,
      "totaltokens": null,
      "tokencost": null,
      "processedms": null,
      "model": null,
      "provider": "prepaid",
      "providerref": null,
      "metadata": { "period": "2026-05", "priceUsd": 90 },
      "uuid": "ada-uuid",
      "user": {
        "uuid": "ada-uuid",
        "firstname": "Ada",
        "lastname": "Lovelace",
        "email": "ada@example.com",
        "username": "ada",
        "avatar": "https://cdn.wiro.ai/avatars/ada.webp",
        "avatarinitials": "AL"
      },
      "createdat": 1714608000
    },
    {
      "guid": "3c4d5e6f-7a8b-4c9d-8e0f-1a2b3c4d5e04",
      "type": "grant",
      "action": "skill-toggle",
      "amount": 667,
      "balanceafter": 4717,
      "description": "Skill enabled: int-instagram-post",
      "sessionkey": null,
      "messageguid": null,
      "inputtokens": null,
      "outputtokens": null,
      "cachereadtokens": null,
      "cachewritetokens": null,
      "totaltokens": null,
      "tokencost": null,
      "processedms": null,
      "model": null,
      "provider": "prepaid",
      "providerref": null,
      "metadata": {
        "skill": "int-instagram-post",
        "enabled": true,
        "previousPriceUsd": 80,
        "newPriceUsd": 90,
        "proratedCharge": 6.67,
        "usedCreditsUsd": 0,
        "creditsFromRenewal": 9000
      },
      "uuid": "ada-uuid",
      "user": {
        "uuid": "ada-uuid",
        "firstname": "Ada",
        "lastname": "Lovelace",
        "email": "ada@example.com",
        "username": "ada",
        "avatar": "https://cdn.wiro.ai/avatars/ada.webp",
        "avatarinitials": "AL"
      },
      "createdat": 1712880000
    }
  ]
}

The user object is the resolved actor that triggered the ledger entry: uuid, firstname, lastname, email, username, avatar, avatarinitials. Every row is recorded for an account uuid (renewals, expiry sweeps and other automated rows carry the agent owner's), so user is null only when that user record has been deleted.

Summary fields

FieldTypeDescription
monthlycreditsnumberThe credits this period grants.
renewalcreditsnumberThe credits each renewal grants.
extracreditsnumberActive extra-credit balance (sum of non-expired packs).
usedcreditsnumberConsumed during the current period (reported by the runtime).
remainingcreditsnumbermax(0, monthlycredits + extracredits - usedcredits).
creditperiodstring'YYYY-MM' billing window. Rolls over on subscription renewal.
creditsyncatnumber|nullUnix seconds of the last monthly usage reset, the start of the current credit period; byModel counts turns from this moment on (null until the first reset).
extrapacksarrayThe agent's extra-credit packs: { id, credits, remainingcredits, status, provider, purchasedat, expiresat }. Active packs are used oldest first.
byModelarrayPer-model token rollup for the current billing period, aggregating only action: "tokens" rows, sorted by tokencost descending. A null or empty model is bucketed as "unknown". Each entry: { model, inputtokens, outputtokens, cachereadtokens, cachewritetokens, totaltokens, tokencost, turncount }. Within a period that stays inside monthlycredits, the tokencost values add up to usedcredits; credits spent from extra packs are counted here but come off extracredits instead.

Transaction fields

FieldTypeDescription
guidstringStable id of the ledger row. Daemon retries reuse this guid for idempotency.
typestringOne of "deduct", "renewal", "purchase", "grant", "expired", "refund", "adjust". There is no "cancel" type: a user-initiated subscription cancel only flips auto-renew off; credits are not forfeited until currentperiodend is reached, at which point the cron writes the ledger row as type: "expired", action: "subscription".
actionstring|nullFine-grained detail. Values depend on type: "tokens" (deduct: the per-turn LLM token charge Wiro records for every chat, cron, and voice post-call turn); "monthly" (renewal); "small", "medium", "large" (purchase); "skill-toggle", "skills-apply", "upgrade", "tier-change" (grant); "skill-toggle", "skills-apply" (expired, mid-period skill disable); "subscription" (expired, end-of-period cancel rollover; refund); "skill-change-not-undone", "skill-credit-failed" (adjust). Legacy deduct rows may also carry "message", "create", "modify", or "regenerate".
amountnumberSigned credit delta: negative for deductions, positive for grants.
balanceafternumber|nullRemaining credit balance snapshot written at the time of the event. null on per-turn deduct rows.
descriptionstring|nullHuman-readable label. On an action: "tokens" row it spells the counts out: LLM <model> (uncached in N, cached in N, out N, cache write N, total N tokens), where cached in and cache write appear only above 0.
sessionkeystring|nullSession the deduct belongs to (deduct rows only).
agentsessionkeystring|nullAlias of sessionkey (same value), present on every row for a unified per-session attribution key across TaskList / TransactionList / wallet responses.
messageguidstring|nullAgent message that triggered the deduct (deduct rows only).
inputtokensnumber|nullUncached prompt tokens billed at the model's input rate (action: "tokens" rows only).
outputtokensnumber|nullCompletion tokens billed for the turn (action: "tokens" rows only).
cachereadtokensnumber|nullPrompt tokens served from cache for the turn (action: "tokens" rows only).
cachewritetokensnumber|nullPrompt tokens written to cache for the turn (action: "tokens" rows only).
totaltokensnumber|nullExact inputtokens + outputtokens + cachereadtokens + cachewritetokens sum (action: "tokens" rows only).
tokencostnumber|nullCredit cost for the turn; equals abs(amount) (action: "tokens" rows only).
processedmsnumber|nullModel processing time for the turn, in milliseconds (action: "tokens" rows only).
modelstring|nullModel id that served the turn, e.g. "openai/gpt-5.4" (action: "tokens" rows only).
providerstring|null"agent" (runtime deduct) or "prepaid" (wallet-backed change). API-deployed agents always run on the prepaid path.
providerrefstring|nullProvider-side reference id. Always populated for prepaid rows (wallet transaction id); null for agent runtime deducts.
metadataobject|nullArbitrary JSON context (skill, tier, period, prorated charge, …).
uuidstring|nullUUID of the account the row is recorded for: the user who acted, or the agent\'s owner for renewals and other automated rows. Never a "system" placeholder.
userobject|nullResolved actor: { uuid, firstname, lastname, email, username, avatar, avatarinitials }. null when the user record was deleted.
createdatnumberUnix seconds.
Pagination. The endpoint returns total so you can render a precise paginator. Default page size is 50, max is 500. Use start to skip rows.

Reading the Ledger: Patterns

Daily / weekly reports

Filter client-side on createdat to bucket transactions per day or per skill:

import requests
from collections import defaultdict
from datetime import datetime

resp = requests.post(
    "https://api.wiro.ai/v1/UserAgent/TransactionList",
    headers={"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"},
    json={"useragentguid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321", "limit": 500}
).json()

per_day_burn = defaultdict(int)
for tx in resp["transactions"]:
    if tx["type"] == "deduct":
        day = datetime.utcfromtimestamp(tx["createdat"]).strftime("%Y-%m-%d")
        per_day_burn[day] += abs(tx["amount"])

for day in sorted(per_day_burn):
    print(f"{day}: {per_day_burn[day]} credits")

Audit a billing-period rollover

type: "renewal" rows are written exactly once per billing-period rollover. Pair them with the most recent summary.creditperiod to confirm the new period started on time.

const renewals = transactions.filter(t => t.type === 'renewal');
const lastRenewal = renewals[0];   // newest-first ordering
console.log('Last renewal:', new Date(lastRenewal.createdat * 1000).toISOString());
console.log('Granted:', lastRenewal.amount, 'credits');
console.log('Now in period:', summary.creditperiod);

Reconcile a Pro tier upgrade

When the user upgrades Starter → Pro mid-period, the ledger gets one grant row for the credits the upgrade added to this period. For a wallet-billed (prepaid) agent, UpgradeTier writes action: "tier-change" with those credits as amount (the monthly difference for the time left in the period), a description like "Tier starter → pro" and metadata: { fromTier, toTier, previousPriceUsd, newPriceUsd, proratedCharge, creditsFromRenewal }; a card-billed upgrade writes action: "upgrade". Use these rows to render an upgrade history in your UI.

Errors

ErrorWhen
useragentguid is requiredTransactionList without useragentguid
useragent-access-deniedCaller is neither the owner nor on the agent's team
useragent-team-admin-requiredCaller is a member of the agent's team but not a team admin (code 97)
Invalid credentialsAPI key is missing or doesn't resolve to a valid Wiro user
transactions-list-failedTransactionList server-side error (DB): surfaced loudly so it's distinguishable from "empty ledger"

Code Examples

curl

# Latest 50 transactions for a useragent
curl -X POST "https://api.wiro.ai/v1/UserAgent/TransactionList" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "useragentguid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321", "limit": 50 }'

# Page 2 (next 50)
curl -X POST "https://api.wiro.ai/v1/UserAgent/TransactionList" \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "useragentguid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321", "limit": 50, "start": 50 }'

Python

import requests

headers = {"x-api-key": "YOUR_API_KEY", "Content-Type": "application/json"}

resp = requests.post(
    "https://api.wiro.ai/v1/UserAgent/TransactionList",
    headers=headers,
    json={"useragentguid": "f8e7d6c5-b4a3-2190-fedc-ba0987654321", "limit": 100}
).json()

print(f"Balance — remaining: {resp['summary']['remainingcredits']}/{resp['summary']['monthlycredits']}")
print(f"Total ledger rows: {resp['total']}")

for tx in resp["transactions"][:10]:
    sign = "+" if tx["amount"] > 0 else ""
    print(f"  [{tx['type']:>9}] {sign}{tx['amount']:>6}  {tx['description']}")

Node.js

const axios = require('axios');

const headers = { 'x-api-key': 'YOUR_API_KEY', 'Content-Type': 'application/json' };

const { data } = await axios.post(
  'https://api.wiro.ai/v1/UserAgent/TransactionList',
  { useragentguid: 'f8e7d6c5-b4a3-2190-fedc-ba0987654321', limit: 100 },
  { headers }
);

console.log(`Balance: ${data.summary.remainingcredits}/${data.summary.monthlycredits}`);
console.log(`Period: ${data.summary.creditperiod}`);

data.transactions.slice(0, 10).forEach(tx => {
  const sign = tx.amount > 0 ? '+' : '';
  console.log(`  [${tx.type.padStart(9)}] ${sign}${tx.amount}  ${tx.description}`);
});

PHP

<?php
$ch = curl_init("https://api.wiro.ai/v1/UserAgent/TransactionList");
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    "Content-Type: application/json",
    "x-api-key: YOUR_API_KEY"
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
    "useragentguid" => "f8e7d6c5-b4a3-2190-fedc-ba0987654321",
    "limit" => 100
]));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$resp = json_decode(curl_exec($ch), true);
curl_close($ch);

echo "Balance: {$resp['summary']['remainingcredits']}/{$resp['summary']['monthlycredits']}\n";
foreach (array_slice($resp['transactions'], 0, 10) as $tx) {
    $sign = $tx['amount'] > 0 ? '+' : '';
    echo "[{$tx['type']}] {$sign}{$tx['amount']} {$tx['description']}\n";
}

What's Next

  • Agent Logs: Activity feed (tool calls, cron runs, message exchanges) for the same useragent
  • Agent Overview: Full agent endpoint catalog, including subscription / billing operations
  • Agent Builder: Build a custom agent and watch the ledger fill up as it runs