Skip to content

Endpoints — Sessions

A session is a row in radacct — one PPPoE connect/disconnect cycle. The Sessions API exposes the live view (sessions where acctstoptime IS NULL) plus disconnect controls. Closed/historical sessions are reachable from the same list endpoint with a status filter, and bulk-exported as CSV.

https://your-panel-host/api/sessions

Authorization: Bearer <jwt> required. Reseller scoping: a reseller sees only sessions belonging to subscribers in their tree. sessions.view_all permission flips a reseller to the admin scope.

{
"radacctid": 12345678,
"username": "user1@example.lb",
"acctsessionid": "8a3f9c01",
"nasipaddress": "<bng-private>",
"nas_id": 2,
"nasportid": "ether2",
"framedipaddress": "<subscriber-ip>",
"callingstationid": "AA:BB:CC:DD:EE:01",
"acctstarttime": "2026-05-12T08:14:22Z",
"acctstoptime": null,
"acctupdatetime": "2026-05-12T11:01:03Z",
"acctinputoctets": 1234567890,
"acctoutputoctets": 9876543210,
"acctsessiontime": 10241,
"acctterminatecause": null
}

The DB column names are PostgreSQL-style: lowercase, no underscores (acctstarttime, not acct_start_time). The API echoes the same names so accounting tooling stays compatible.

List sessions.

Permission: sessions.view

Query parameters

ParamTypeDefaultDescription
statusstringactiveactive (default — open sessions), closed, all
pageint11-indexed
limitint50Max 500
searchstringMatch on username / framedipaddress / callingstationid
nas_idintFilter by NAS that authorised the session
fromstringISO datetime — sessions started ≥ this
tostringISO datetime — sessions started ≤ this
sortstringacctstarttimeacctstarttime, acctinputoctets, acctoutputoctets, acctsessiontime
orderstringdescasc / desc

Response — 200 OK

{
"success": true,
"data": [ { "radacctid": 12345678, "...": "..." } ],
"pagination": { "page": 1, "limit": 50, "total": 1842 }
}
Terminal window
curl "https://panel.example.com/api/sessions?status=active&limit=100" \
-H "Authorization: Bearer ..."

Errors

StatusmessageCause
400invalid status, must be active/closed/all
403sessions.view deniedCaller lacks the permission

Quick counter — number of currently active sessions in caller’s scope. Used by the Dashboard.

Permission: sessions.view

Response — 200 OK

{ "success": true, "count": 1842 }

The count is not cached server-side — it is a SELECT COUNT(*) against radacct WHERE acctstoptime IS NULL. At 25 k subscribers this query runs in ~5 ms with the acctstoptime IS NULL partial index. (This index is ensured at startup via EnsureIndexes.)

Return a single session by radacctid.

Permission: sessions.view

Terminal window
curl https://panel.example.com/api/sessions/12345678 \
-H "Authorization: Bearer ..."

Errors

StatusmessageCause
404session not foundradacctid does not exist
403not authorizedReseller asked for a session outside their scope

Terminate a live session. The handler:

  1. Looks up the nasipaddress for the session.
  2. Sends a RADIUS CoA Disconnect-Request to that NAS on the NAS’s coa_port (default 1700 for MikroTik).
  3. Falls back to MikroTik API /ppp/active/remove if CoA times out (3 s).
  4. Writes an Acct-Stop row when the NAS acknowledges, otherwise lets the Stale-Session-Cleanup sweeper close it within 5 minutes.

Permission: subscribers.disconnect

The verb is POST, not DELETE, because the action has side-effects beyond removing a row — the subscriber is disconnected from their PPPoE link.

Request

POST /api/sessions/12345678/disconnect
Authorization: Bearer <jwt>

No body.

Response — 200 OK

{
"success": true,
"method": "coa",
"message": "disconnect request sent"
}

method is coa when CoA succeeded, api when the MikroTik-API fallback kicked in, or already_closed when the session was already gone by the time we looked. The endpoint is idempotent — repeat calls on a closed session return already_closed with 200.

Errors

StatusmessageCause
404session not foundBad id
503NAS unreachableBoth CoA and MikroTik-API attempts failed (rare)
403subscribers.disconnect deniedCaller lacks the permission

Export sessions matching a filter as CSV. Used by the Sessions → Export button and by integration scripts.

Permission: sessions.view

Query parameters — same as GET /api/sessions, plus:

ParamTypeDefaultDescription
formatstringcsvcsv only currently; reserved for xlsx later
columnsstring(all)Comma-list to restrict columns

Response — 200 OKContent-Type: text/csv with a Content-Disposition: attachment header.

Terminal window
curl -OJ "https://panel.example.com/api/sessions/export?status=closed&from=2026-05-01T00:00:00Z&to=2026-05-12T23:59:59Z" \
-H "Authorization: Bearer ..."

Sample CSV:

radacctid,username,nasipaddress,framedipaddress,acctstarttime,acctstoptime,acctsessiontime,acctinputoctets,acctoutputoctets,acctterminatecause
12345678,user1@example.lb,<bng-private>,<subscriber-ip>,2026-05-12T08:14:22Z,2026-05-12T12:18:14Z,14632,1234567890,9876543210,User-Request

Limits

  • Max 500,000 rows per export. Requests beyond that return 413 too many rows, narrow date range.
  • Streams the response — does not load the full set into memory. A 500 k-row CSV is ~80 MB and takes 30–60 s to deliver.
  • Use from / to aggressively when working with radacct_archive (anything older than 90 days has been moved there and falls under a separate archival query path).

The API container runs StaleSessionCleanupService every 5 minutes. It closes sessions where:

acctstoptime IS NULL AND acctupdatetime < NOW() - INTERVAL '30 minutes'

This is what stops “ghost” sessions accumulating when a MikroTik reboots without sending Acct-Stop packets. The closed rows get acctterminatecause = 'NAS-Idle-Timeout'. The sweeper also syncs the subscribers.is_online flag — if a user has no open radacct row they are marked offline regardless of what is_online previously said.

Closed rows older than 90 days are archived to radacct_archive by RadAcctArchivalService (runs daily at 03:00). The Sessions API does not automatically union the archive — to query closed sessions older than 90 d, hit radacct_archive directly or use the Reports page which switches to the archive when the range demands it.

{ "success": false, "message": "session not found" }
StatusMeaning
400Validation failure on query params
401Missing / expired JWT
403Permission denied
404Bad id
413Export row count exceeded
503NAS unreachable on disconnect

300 req/min/IP global. The export endpoint is not rate-limited differently, but the underlying handler holds the DB connection for the duration of the stream — fire-and-forget hundreds of exports in parallel will exhaust the connection pool. One export at a time per integration is sensible.