Choose the records you control
This recipe exports the application's own usage-events.jsonl file from the user-tracking recipe. It creates a CSV for operational review. It is not an invoice, a complete MandAPI account export or a reconstruction of actual charges.
Use your account usage and statistics views to check platform activity. This script works only with your own application log; it does not use a platform export button or public export endpoint. Check actual account credits in the MandAPI dashboard.
Save and try the exporter
Save this as export_activity.py. Python's standard library is sufficient. Start with --demo; it reads no account data and performs no network requests.
import argparse
import csv
from datetime import datetime
import json
from pathlib import Path
FIELDS = ("record_type", "time", "request_id", "feature", "model", "status",
"prompt_tokens", "completion_tokens", "duration_ms")
NUMBERS = {"prompt_tokens", "completion_tokens", "duration_ms"}
def safe_text(value):
if not isinstance(value, str) or len(value) > 200:
raise ValueError("Expected a short text field")
text = value.replace("\r", " ").replace("\n", " ")
return "'" + text if text.lstrip().startswith(("=", "+", "-", "@")) else text
def clean(event):
if not isinstance(event, dict) or event.get("status") not in {"ok", "error"}:
raise ValueError("Invalid event status")
timestamp = datetime.fromisoformat(event["time"])
if timestamp.tzinfo is None:
raise ValueError("Timestamp must include a time zone")
row = {}
for field in FIELDS:
if field == "record_type":
row[field] = "application_usage_not_billing"
elif field in NUMBERS:
value = event.get(field)
if value is not None and (type(value) is not int or value < 0):
raise ValueError("Token and duration fields must be nonnegative integers")
row[field] = "" if value is None else value
else:
row[field] = safe_text(event.get(field, ""))
return row
parser = argparse.ArgumentParser()
parser.add_argument("source", nargs="?")
parser.add_argument("--output", default="usage-activity.csv")
parser.add_argument("--demo", action="store_true")
args = parser.parse_args()
if args.demo:
events = [
{"time": "2026-10-30T09:00:00+00:00", "request_id": "demo-1", "feature": "support", "model": "MODEL_ID", "status": "ok", "prompt_tokens": 12, "completion_tokens": 5, "duration_ms": 400},
{"time": "2026-10-30T09:01:00+00:00", "request_id": "demo-2", "feature": "=2+2", "status": "error", "duration_ms": 200, "api_key": "DROP_THIS_FIELD"},
]
else:
if not args.source:
parser.error("Provide your own usage-events.jsonl or choose --demo")
with Path(args.source).open(encoding="utf-8") as handle:
events = [json.loads(line) for line in handle if line.strip()]
rows = [clean(event) for event in events]
with Path(args.output).open("x", newline="", encoding="utf-8-sig") as handle:
writer = csv.DictWriter(handle, fieldnames=FIELDS)
writer.writeheader()
writer.writerows(rows)
print(json.dumps({"rows": len(rows), "output": args.output, "billing_report": False}))python export_activity.py --demo --output usage-demo.csvThe demo writes two rows. The failed request has blank token cells because its usage is unknown; blank is different from zero. Its extra api_key field is discarded by the allowlist, and a formula-like feature value is written as text.
The output file must not already exist. Review spreadsheets in the program you use: prefixing formula-like text is a basic protective measure, not a replacement for careful handling of untrusted spreadsheets.
Export your own application log
Use a local copy containing only records you are entitled to process. The earlier tracking example uses timezone-aware timestamps, one application request ID, status and any returned token usage.
python export_activity.py usage-events.jsonl --output usage-activity.csvOnly listed columns are exported. The script omits prompts, answers, keys, user identifiers and arbitrary extra fields. It rejects invalid status, timezone-free timestamps and negative or noninteger token counts. It does not change the source log.
For a large service, export an authorized, bounded query from your application's database instead of loading an unbounded log file. Define who may download the report and how long it is retained.
Reconcile and check the result
Check row counts against the chosen source interval. Missing events, retries, streaming usage gaps and requests from other applications can make this report incomplete. A model token count does not by itself establish the credit deducted.
The demo CSV must contain exactly two data rows, a fixed application_usage_not_billing marker and blank usage for the failed request. Confirm the unexpected api_key field is absent. Use actual dashboard records to investigate account billing differences; do not infer money totals by treating unknown usage as zero.