MandAPI
English ▾
Keys and usage

Export your application's usage events as CSV

Turn your own JSONL records into a minimal spreadsheet report without treating it as a platform bill.

Use this recipe with your coding assistant

Read the prompt
Create an offline CSV export of my application's own usage-events.jsonl records. Use the known user-tracking schema and an explicit column allowlist. Exclude prompts, replies, API keys, user identifiers and arbitrary extra fields; keep missing usage blank, validate nonnegative integer counts and timezone-aware timestamps, and neutralize formula-like text cells. Mark records as application usage, not billing. Do not invent a MandAPI export endpoint, public analytics API, dashboard export button or monetary totals. Preserve the source and avoid overwriting outputs. Validate with two synthetic events, including missing usage, an extra secret-like field and a formula-like value.
TXT

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.

python
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}))
powershell
python export_activity.py --demo --output usage-demo.csv

The 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.

powershell
python export_activity.py usage-events.jsonl --output usage-activity.csv

Only 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.

References

Topics inspired by public documentation; instructions and examples adapted for MandAPI.