Skip to content

Subscriber - Usage & Sessions ​

Base URL: https://your-domain.com

The signed-in subscriber's data usage, connection sessions and connection (RADIUS sign-in) history. Paging and date formats are described under Conventions.


Subscriber Usage ​

Get the subscriber's download and upload traffic for a period, as totals and a series of points for a graph.

PropertyValue
EndpointSubscriber Usage
MethodGET
URL/api/v1/subscriber/usage
AuthenticationBearer Token (Subscriber)

Request Headers:

HeaderValueRequired
Acceptapplication/jsonYes
AuthorizationBearer subscriber-tokenYes

Query Parameters:

ParameterTypeRequiredDescription
periodstringNo24h, 7d or 30d (default 30d)

Request Body: None

Success Response (200 OK):

json
{
    "status": "success",
    "data": {
        "period": "7d",
        "bucket": "day",
        "download_gb": 28.4,
        "upload_gb": 3.05,
        "points": [
            { "start": "2026-10-18T00:00:00+06:00", "download_gb": 3.9, "upload_gb": 0.41 },
            { "start": "2026-10-19T00:00:00+06:00", "download_gb": 4.6, "upload_gb": 0.52 },
            { "start": "2026-10-20T00:00:00+06:00", "download_gb": 0, "upload_gb": 0 },
            { "start": "2026-10-21T00:00:00+06:00", "download_gb": 5.2, "upload_gb": 0.55 },
            { "start": "2026-10-22T00:00:00+06:00", "download_gb": 4.8, "upload_gb": 0.5 },
            { "start": "2026-10-23T00:00:00+06:00", "download_gb": 6.1, "upload_gb": 0.63 },
            { "start": "2026-10-24T00:00:00+06:00", "download_gb": 3.8, "upload_gb": 0.44 }
        ]
    }
}

Field notes:

  • 24h returns 24 hourly points (bucket is hour) ending with the current hour. 7d and 30d return 7 or 30 daily points (bucket is day) ending today.
  • Every point is present; a period with no traffic is 0.
  • start is the start of the hour or day. Amounts are in GB (1024³ bytes), rounded to 2 decimals. download_gb and upload_gb at the top are the sums of the points.

Error Response (422 Unprocessable Entity):

json
{
    "message": "The selected period is invalid.",
    "errors": {
        "period": [
            "The selected period is invalid."
        ]
    }
}

Code Examples:

bash
curl -X GET "https://your-domain.com/api/v1/subscriber/usage?period=7d" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer your-auth-token"
php
<?php

require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://your-domain.com']);

$response = $client->request('GET', '/api/v1/subscriber/usage', [
    'headers' => [
        'Accept'        => 'application/json',
        'Authorization' => 'Bearer your-auth-token',
    ],
    'query' => [
        'period' => '7d',
    ],
]);

$data = json_decode($response->getBody(), true);
print_r($data);
javascript
const BASE_URL = 'https://your-domain.com';
const TOKEN = 'your-auth-token';

const params = new URLSearchParams({
  period: '7d',
});

const response = await fetch(`${BASE_URL}/api/v1/subscriber/usage?${params}`, {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    Authorization: `Bearer ${TOKEN}`,
  },
});

const data = await response.json();
console.log(data);
python
import requests

BASE_URL = "https://your-domain.com"
TOKEN = "your-auth-token"

response = requests.get(
    f"{BASE_URL}/api/v1/subscriber/usage",
    headers={
        "Accept": "application/json",
        "Authorization": f"Bearer {TOKEN}",
    },
    params={
        "period": "7d",
    },
)

data = response.json()
print(data)
go
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
)

const baseURL = "https://your-domain.com"
const token = "your-auth-token"

func main() {
	endpoint := baseURL + "/api/v1/subscriber/usage"

	q := url.Values{}
	q.Set("period", "7d")
	endpoint += "?" + q.Encode()

	req, _ := http.NewRequest("GET", endpoint, nil)
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.StatusCode, string(body))
}
rust
use reqwest::Client;

const BASE_URL: &str = "https://your-domain.com";
const TOKEN: &str = "your-auth-token";

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let client = Client::new();

    let response = client
        .get(format!("{BASE_URL}/api/v1/subscriber/usage"))
        .header("Accept", "application/json")
        .bearer_auth(TOKEN)
        .query(&[
            ("period", "7d"),
        ])
        .send()
        .await?;

    let data: serde_json::Value = response.json().await?;
    println!("{data:#}");
    Ok(())
}

Subscriber Sessions ​

List the subscriber's connection sessions (RADIUS accounting records), newest first.

PropertyValue
EndpointSubscriber Sessions
MethodGET
URL/api/v1/subscriber/sessions
AuthenticationBearer Token (Subscriber)

Request Headers:

HeaderValueRequired
Acceptapplication/jsonYes
AuthorizationBearer subscriber-tokenYes

Query Parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default 1)
per_pageintegerNoItems per page, 1 to 50 (default 20)

Request Body: None

Success Response (200 OK):

json
{
    "status": "success",
    "data": [
        {
            "id": 884213,
            "started_at": "2026-10-24T06:00:11+06:00",
            "stopped_at": null,
            "online": true,
            "duration_seconds": 12300,
            "ip": "100.64.12.34",
            "mac": "A4:2B:B0:11:7C:9E",
            "download_bytes": 2147483648,
            "upload_bytes": 268435456,
            "terminate_cause": null
        },
        {
            "id": 883950,
            "started_at": "2026-10-23T18:42:05+06:00",
            "stopped_at": "2026-10-24T05:59:40+06:00",
            "online": false,
            "duration_seconds": 40655,
            "ip": "100.64.12.34",
            "mac": "A4:2B:B0:11:7C:9E",
            "download_bytes": 6012954214,
            "upload_bytes": 644245094,
            "terminate_cause": "User-Request"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 9,
        "per_page": 20,
        "total": 172
    }
}

Field notes:

  • online is true while the session has no stop time.
  • duration_seconds, download_bytes and upload_bytes are the values the router last reported. Download is traffic to the subscriber.
  • ip is the address given to the subscriber's connection and mac the device MAC the router reported.
  • terminate_cause is the RADIUS reason the session ended (for example User-Request, Lost-Carrier, Session-Timeout), or null.

Error Response (401 Unauthorized):

json
{
    "message": "Unauthenticated."
}

Code Examples:

bash
curl -X GET "https://your-domain.com/api/v1/subscriber/sessions?page=1&per_page=20" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer your-auth-token"
php
<?php

require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://your-domain.com']);

$response = $client->request('GET', '/api/v1/subscriber/sessions', [
    'headers' => [
        'Accept'        => 'application/json',
        'Authorization' => 'Bearer your-auth-token',
    ],
    'query' => [
        'page'     => 1,
        'per_page' => 20,
    ],
]);

$data = json_decode($response->getBody(), true);
print_r($data);
javascript
const BASE_URL = 'https://your-domain.com';
const TOKEN = 'your-auth-token';

const params = new URLSearchParams({
  page: 1,
  per_page: 20,
});

const response = await fetch(`${BASE_URL}/api/v1/subscriber/sessions?${params}`, {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    Authorization: `Bearer ${TOKEN}`,
  },
});

const data = await response.json();
console.log(data);
python
import requests

BASE_URL = "https://your-domain.com"
TOKEN = "your-auth-token"

response = requests.get(
    f"{BASE_URL}/api/v1/subscriber/sessions",
    headers={
        "Accept": "application/json",
        "Authorization": f"Bearer {TOKEN}",
    },
    params={
        "page": 1,
        "per_page": 20,
    },
)

data = response.json()
print(data)
go
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
)

const baseURL = "https://your-domain.com"
const token = "your-auth-token"

func main() {
	endpoint := baseURL + "/api/v1/subscriber/sessions"

	q := url.Values{}
	q.Set("page", "1")
	q.Set("per_page", "20")
	endpoint += "?" + q.Encode()

	req, _ := http.NewRequest("GET", endpoint, nil)
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.StatusCode, string(body))
}
rust
use reqwest::Client;

const BASE_URL: &str = "https://your-domain.com";
const TOKEN: &str = "your-auth-token";

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let client = Client::new();

    let response = client
        .get(format!("{BASE_URL}/api/v1/subscriber/sessions"))
        .header("Accept", "application/json")
        .bearer_auth(TOKEN)
        .query(&[
            ("page", "1"),
            ("per_page", "20"),
        ])
        .send()
        .await?;

    let data: serde_json::Value = response.json().await?;
    println!("{data:#}");
    Ok(())
}

Subscriber Connection Logs ​

List the RADIUS sign-in attempts of the subscriber's connection, newest first. The password and the router's address are never included.

PropertyValue
EndpointSubscriber Connection Logs
MethodGET
URL/api/v1/subscriber/connection-logs
AuthenticationBearer Token (Subscriber)

Request Headers:

HeaderValueRequired
Acceptapplication/jsonYes
AuthorizationBearer subscriber-tokenYes

Query Parameters:

ParameterTypeRequiredDescription
pageintegerNoPage number (default 1)
per_pageintegerNoItems per page, 1 to 50 (default 20)

Request Body: None

Success Response (200 OK):

json
{
    "status": "success",
    "data": [
        {
            "id": 551204,
            "at": "2026-10-24T06:00:10+06:00",
            "result": "accepted",
            "reason": null,
            "mac": "A4:2B:B0:11:7C:9E"
        },
        {
            "id": 550987,
            "at": "2026-10-02T09:58:31+06:00",
            "result": "accepted",
            "reason": "Expired - expired_pool",
            "mac": "A4:2B:B0:11:7C:9E"
        },
        {
            "id": 550912,
            "at": "2026-10-02T09:41:07+06:00",
            "result": "rejected",
            "reason": null,
            "mac": "A4:2B:B0:11:7C:9E"
        }
    ],
    "meta": {
        "current_page": 1,
        "last_page": 12,
        "per_page": 20,
        "total": 231
    }
}

Field notes:

  • result is accepted (Access-Accept), rejected (Access-Reject) or other.
  • reason is the text FreeRADIUS logged after the result (for example Expired - expired_pool or Cleared Stale Session), or null when there is none.
  • mac is the device MAC, or null when the router did not send one.

Error Response (401 Unauthorized):

json
{
    "message": "Unauthenticated."
}

Code Examples:

bash
curl -X GET "https://your-domain.com/api/v1/subscriber/connection-logs?page=1&per_page=20" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer your-auth-token"
php
<?php

require 'vendor/autoload.php';

use GuzzleHttp\Client;

$client = new Client(['base_uri' => 'https://your-domain.com']);

$response = $client->request('GET', '/api/v1/subscriber/connection-logs', [
    'headers' => [
        'Accept'        => 'application/json',
        'Authorization' => 'Bearer your-auth-token',
    ],
    'query' => [
        'page'     => 1,
        'per_page' => 20,
    ],
]);

$data = json_decode($response->getBody(), true);
print_r($data);
javascript
const BASE_URL = 'https://your-domain.com';
const TOKEN = 'your-auth-token';

const params = new URLSearchParams({
  page: 1,
  per_page: 20,
});

const response = await fetch(`${BASE_URL}/api/v1/subscriber/connection-logs?${params}`, {
  method: 'GET',
  headers: {
    Accept: 'application/json',
    Authorization: `Bearer ${TOKEN}`,
  },
});

const data = await response.json();
console.log(data);
python
import requests

BASE_URL = "https://your-domain.com"
TOKEN = "your-auth-token"

response = requests.get(
    f"{BASE_URL}/api/v1/subscriber/connection-logs",
    headers={
        "Accept": "application/json",
        "Authorization": f"Bearer {TOKEN}",
    },
    params={
        "page": 1,
        "per_page": 20,
    },
)

data = response.json()
print(data)
go
package main

import (
	"fmt"
	"io"
	"net/http"
	"net/url"
)

const baseURL = "https://your-domain.com"
const token = "your-auth-token"

func main() {
	endpoint := baseURL + "/api/v1/subscriber/connection-logs"

	q := url.Values{}
	q.Set("page", "1")
	q.Set("per_page", "20")
	endpoint += "?" + q.Encode()

	req, _ := http.NewRequest("GET", endpoint, nil)
	req.Header.Set("Accept", "application/json")
	req.Header.Set("Authorization", "Bearer "+token)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer resp.Body.Close()

	body, _ := io.ReadAll(resp.Body)
	fmt.Println(resp.StatusCode, string(body))
}
rust
use reqwest::Client;

const BASE_URL: &str = "https://your-domain.com";
const TOKEN: &str = "your-auth-token";

#[tokio::main]
async fn main() -> Result<(), reqwest::Error> {
    let client = Client::new();

    let response = client
        .get(format!("{BASE_URL}/api/v1/subscriber/connection-logs"))
        .header("Accept", "application/json")
        .bearer_auth(TOKEN)
        .query(&[
            ("page", "1"),
            ("per_page", "20"),
        ])
        .send()
        .await?;

    let data: serde_json::Value = response.json().await?;
    println!("{data:#}");
    Ok(())
}

www.onezeroart.com