# Admin metrics

> Staff-only platform metrics: GET /api/admin/metrics/, its full response shape, and the 403 admin_required returned to non-staff accounts.

Figgshield exposes one staff-only endpoint for platform metrics. It backs the internal admin dashboard (run locally, not part of the public app) and is not part of the normal customer API surface — ordinary accounts never reach it.

Staff accounts are created **out-of-band**, through the Django admin or shell; there is no signup or upgrade path to staff. Every ordinary account carries `is_staff: false` in its [user object](https://figgshield.ai/documentation/authentication.md); staff accounts carry `is_staff: true`.

## Platform metrics

`GET /api/admin/metrics/` — requires a staff account. Authenticate with a staff session cookie or an API key belonging to a staff account.

```bash
curl https://api.figgshield.ai/api/admin/metrics/ \
  -H "Authorization: Bearer $FIGGSHIELD_API_KEY"
```

A non-staff caller — even a perfectly valid, authenticated one — receives `403` with the machine code `admin_required`:

```json
{ "error": "admin_required", "detail": "This endpoint requires a staff account." }
```

The response aggregates users, subscriptions, revenue, provider costs and margins, plus 30-day daily and per-model breakdowns. All monetary amounts are integer cents.

```json
{
  "generated_at": "2026-07-06T09:00:00Z",
  "users": {
    "total": 4120,
    "new_30d": 318,
    "onboarding": { "profile": 44, "plan": 61, "complete": 4015 }
  },
  "subscriptions": {
    "active_total": 2870,
    "cancel_at_period_end": 63,
    "by_plan": [
      { "slug": "starter", "name": "Starter", "count": 980, "yearly_cents": 14400 },
      { "slug": "pro", "name": "Pro", "count": 1080, "yearly_cents": 34800 },
      { "slug": "studio", "name": "Studio", "count": 170, "yearly_cents": 90000 }
    ]
  },
  "revenue": {
    "run_rate_yearly_cents": 89078400,
    "effective_monthly_cents": 7423200,
    "topups_30d_cents": 512000
  },
  "costs": {
    "provider_30d_cents": 2140000,
    "per_active_user_monthly_cents": 745
  },
  "margins": {
    "monthly_revenue_cents": 7423200,
    "monthly_cost_cents": 2140000,
    "gross_margin_pct": 71.2
  },
  "daily": [
    { "date": "2026-07-01", "signups": 11, "new_subscriptions": 7, "credits_used": 24310, "provider_cost_cents": 71200 }
  ],
  "by_model": [
    { "model": "Kling", "generations": 1820, "credits": 63700, "provider_cost_cents": 637000 },
    { "model": "Nano Banana 2", "generations": 5140, "credits": 41120, "provider_cost_cents": 174760 }
  ]
}
```

## Response fields

| Group | Fields |
| --- | --- |
| `generated_at` | When these figures were computed. |
| `users` | `total`, `new_30d`, and an `onboarding` funnel counting accounts at each step (`profile`, `plan`, `complete`). |
| `subscriptions` | `active_total`, `cancel_at_period_end`, and `by_plan` — per-plan active counts with each plan's `yearly_cents` (12 × the monthly sticker). |
| `revenue` | `run_rate_yearly_cents`, `effective_monthly_cents` (the run-rate ÷ 12), and `topups_30d_cents`. |
| `costs` | `provider_30d_cents` and `per_active_user_monthly_cents`. |
| `margins` | `monthly_revenue_cents`, `monthly_cost_cents`, and `gross_margin_pct` — `null` when monthly revenue is 0. |
| `daily` | The last 30 days, one entry per day: `signups`, `new_subscriptions`, `credits_used`, `provider_cost_cents`. |
| `by_model` | The last 30 days per model (Kling, Nano Banana 2): `generations`, `credits`, `provider_cost_cents`. |

Provider cost is the best available estimate: the actual per-call cost where the provider reports it, otherwise a per-model admin-set estimate multiplied by the number of calls. The [`GET /api/models/`](https://figgshield.ai/documentation/models.md) public schema never exposes these internal cost figures.
