opsenDocs
Reference

Report JSON

.md

The report as JSON, for dashboards, spreadsheets and alerts.

Every report is available as JSON at two addresses:

URL Auth
https://opsen.dev/o/<org>/report.json Your opsen session cookie (owners only)
https://opsen.dev/r/<token>.json The share token itself; see Sharing

The share-link form is the one to use from scripts: create a link, store it as a secret, and revoke it when the script is retired.

Fetch the yearly figure
curl -s "https://opsen.dev/r/$OPSEN_SHARE_TOKEN.json" | jq '.annual_waste'
Post findings over $100/month to Slack
import json, os, urllib.request

report = json.load(urllib.request.urlopen(
    f"https://opsen.dev/r/{os.environ['OPSEN_SHARE_TOKEN']}.json"))

big = [f for f in report["findings"] if f["monthly_usd"] >= 100]
text = "\n".join(f"• {f['title']}: ${f['monthly_usd']:,.0f}/mo" for f in big)
urllib.request.urlopen(urllib.request.Request(
    os.environ["SLACK_WEBHOOK_URL"], method="POST",
    data=json.dumps({"text": f"{report['org']}: {text}"}).encode(),
    headers={"content-type": "application/json"}))

A 404 means there is no completed report yet, the link was revoked, or the app was uninstalled. The response is the latest completed report; an audit in progress does not change it until it finishes.

Schema

Response
{
  "org": "acme",
  "audit_id": "aud_3f9c1e0b7a2d41",
  "taken_at": 1792655874.9,
  "monthly_waste": 403.12,
  "annual_waste": 4837.44,
  "findings": [
    {
      "id": "copilot.idle",
      "category": "copilot",
      "title": "Copilot seats unused for 30+ days",
      "monthly_usd": 266.0,
      "basis": "GitHub's last_activity_at per seat; seats younger than 30 days are excluded",
      "detail": "Reclaiming sets a seat to pending cancellation: …",
      "items": [
        {"login": "carol", "last_activity": null, "plan_type": "business",
         "via_team": null, "monthly_usd": 19.0, "price_source": "list"}
      ],
      "action": {"type": "reclaim_copilot"}
    }
  ],
  "spend": {
    "plan": "team", "seats": 6, "seats_monthly": 24.0,
    "copilot_credits_last_month": 160.0,
    "copilot_credits_by_model": [{"model": "claude-opus-5-5", "usd": 129.5}],
    "copilot_seats": 16, "copilot_monthly": 304.0,
    "actions_last_month": 42.5, "actions_source": "billing usage report"
  },
  "withheld": [],
  "coverage": {
    "repositories": 2, "repositories_with_workflows": 2, "runs": 13,
    "jobs_priced": 12, "members": 35, "copilot_seats": 16, "errors": 0,
    "truncated": {}, "api_calls": 26, "repository_selection": "all"
  },
  "attribution": null,
  "sources": {
    "gitlab": {
      "name": "GitLab", "total_usd": 444.0, "budget": "platforms",
      "kpis": [["Plan", "Premium"], ["Billable seats", "10"]],
      "lines": [{"name": "6 seats in use", "usd": 174.0},
                {"name": "4 dormant seats", "usd": 116.0, "waste": true, "fid": "gitlab.dormant"}],
      "note": "Premium at $29 a user a month, list price."
    }
  }
}

Timestamps are Unix seconds, UTC. Money is US dollars.

Fields

Field Type
findings[].id string Stable identifier, one of the ids below
findings[].monthly_usd number The finding's contribution to monthly_waste
findings[].basis string How the figure was measured, and whether prices were list or contract
findings[].items array The rows behind the finding; shape depends on id
findings[].category string seats, copilot, actions, or the platform it came from: gitlab, azure_devops, atlassian, jetbrains, circleci, cursor, openai
findings[].action.type string reclaim_copilot, cancel_invitations, disable_workflow, fix_pr, source (a one-click change on a platform, with source and name) or export
withheld[] {id, reason} Findings not reported, and why
coverage.truncated object Limits that were hit, keyed by what was capped
attribution object or null Claude Code spend, when a key is connected
sources object One entry per connected platform: its monthly total_usd, the budget it counts toward, and lines that add up to it. Waste lines carry the fid of their finding

Finding ids

id Documented in
seats.dormant_member Seats
seats.dormant_outside_collaborator Seats
seats.stale_invitations Seats
copilot.idle Copilot
actions.superseded Actions Minutes
actions.dormant_schedules Actions Minutes
actions.hung Actions Minutes

New finding ids may be added. Existing ids and fields are not renamed or removed.