Skip to content

PyroRadius Mobile API Documentation

Document: PHASE2_10_Mobile_API.md Version: 2.0 Date: 2026-07-13 Author: PyroNet Solutions


Overview

The PyroRadius Mobile API provides a RESTful JSON interface for mobile applications and external integrations. It is completely separate from the web (Inertia/session-based) interface and uses Laravel Sanctum token authentication instead of session cookies.

Base URL: https://pyroradius.pyronet.com.pk/api/mobile

Content-Type: All requests must include Content-Type: application/json. All responses are JSON.

Version: The API is currently unversioned. Breaking changes will be announced with sufficient notice.


Authentication

The Mobile API uses Sanctum token-based authentication (Personal Access Tokens). Unlike the web interface (which uses session cookies), every API request must include a Bearer token in the Authorization header.

Authorization: Bearer {your_token}

Tokens are issued at login and must be stored securely by the client. Tokens do not expire automatically but can be revoked via logout.

All permissions are still fully enforced: a dealer user with a valid token can only access their own customers, just as in the web interface.


Rate Limits

Endpoint Group Limit Window
POST /login 5 requests per minute
POST /customers/{id}/recharge 10 requests per minute
All other endpoints Laravel default (60) per minute

When a rate limit is exceeded, the API returns HTTP 429 Too Many Requests with a Retry-After header.


Response Format Standards

Success — Single Resource

{
  "data": {
    "id": 1,
    "field": "value"
  }
}

Success — Paginated List

{
  "data": [
    { "id": 1, "field": "value" },
    { "id": 2, "field": "value" }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 20,
    "total": 150,
    "last_page": 8
  }
}

Error Response

{
  "message": "Human-readable error description",
  "errors": {
    "field_name": ["Validation error message"]
  }
}

The errors key is only present on 422 Unprocessable Entity responses.


HTTP Status Codes

Code Meaning
200 OK — Request succeeded
201 Created — Resource created successfully
204 No Content — Success with no body (e.g., logout)
401 Unauthenticated — Token missing or invalid
403 Forbidden — Authenticated but lacks permission
404 Not Found — Resource does not exist
422 Unprocessable Entity — Validation failed
429 Too Many Requests — Rate limit exceeded
500 Server Error — Internal error

Endpoints


AUTH

LOGIN

Method: POST URL: /api/mobile/login Auth: Public (no token required) Rate Limit: 5 requests/minute

Request Body:

Field Type Required Description
email string Yes User's email address
password string Yes User's password
device_name string No Device identifier for token labeling (default: "mobile")

Example Request:

{
  "email": "admin@pyronet.com.pk",
  "password": "change-me-on-first-login",
  "device_name": "Samsung Galaxy S24"
}

Response (200):

{
  "token": "1|abcdefghijklmnopqrstuvwxyz1234567890",
  "user": {
    "id": 1,
    "name": "Admin User",
    "email": "admin@pyronet.com.pk",
    "role": "super_admin",
    "permissions": [
      "view_customers",
      "create_customers",
      "recharge_customers",
      "view_reports"
    ],
    "dealer_id": null
  }
}

Error Responses:

  • 422: Email or password incorrect
  • 429: Too many login attempts

Notes:

Store the token value securely (e.g., in device secure storage). Include it in the Authorization: Bearer {token} header for all subsequent requests. The permissions array tells the app which UI features to enable/disable.


GET CURRENT USER

Method: GET URL: /api/mobile/me Auth: Required (Bearer token) Rate Limit: Default (60/min)

Request Body: None

Response (200):

{
  "data": {
    "id": 1,
    "name": "Admin User",
    "email": "admin@pyronet.com.pk",
    "role": "super_admin",
    "permissions": [
      "view_customers",
      "create_customers",
      "recharge_customers",
      "edit_customers",
      "delete_customers",
      "view_reports",
      "view_financials",
      "manage_dealers",
      "manage_packages",
      "manage_nas",
      "view_network",
      "send_whatsapp"
    ],
    "dealer_id": null,
    "created_at": "2025-01-15T10:00:00Z"
  }
}

Error Responses:

  • 401: Token invalid or not provided

Notes:

Use this endpoint to refresh user permissions without re-logging in. Also useful after an admin changes a user's permissions — the app can re-fetch to get updated permissions.


LOGOUT

Method: POST URL: /api/mobile/logout Auth: Required (Bearer token) Rate Limit: Default (60/min)

Request Body: None

Response (200):

{
  "message": "Logged out successfully"
}

Error Responses:

  • 401: Token already invalid

Notes:

This revokes the current access token on the server. The client should delete the locally stored token after receiving a success response. To log out all devices, call this endpoint from each device separately (each token is independent).


REFRESH TOKEN

Method: POST URL: /api/mobile/refresh Auth: Required (Bearer token) Rate Limit: Default (60/min)

Request Body:

Field Type Required Description
device_name string No Device name for the new token

Response (200):

{
  "token": "2|newtoken1234567890abcdefghijklmnopqrst",
  "message": "Token refreshed successfully"
}

Error Responses:

  • 401: Current token is invalid

Notes:

Revokes the current token and issues a new one. The old token becomes invalid immediately. The client must replace its stored token with the new value. Useful for rotating tokens periodically for security.


DASHBOARD

GET DASHBOARD STATS

Method: GET URL: /api/mobile/dashboard Auth: Required (Bearer token) Rate Limit: Default (60/min)

Request Parameters: None

Response (200):

{
  "data": {
    "total_customers": 1247,
    "active_customers": 1089,
    "expired_customers": 98,
    "suspended_customers": 43,
    "disabled_customers": 17,
    "online_now": 312,
    "monthly_revenue": 2847500.00,
    "today_revenue": 125000.00,
    "pending_tickets": 8,
    "monthly_collections": {
      "current_month": 2847500.00,
      "last_month": 2634000.00,
      "growth_percent": 8.1
    },
    "recent_recharged": [
      {
        "id": 501,
        "name": "Muhammad Ali",
        "pppoe_username": "ali_01",
        "amount": 2500,
        "recharged_at": "2026-07-13T14:30:00Z"
      }
    ]
  }
}

Error Responses:

  • 401: Unauthenticated
  • 403: Insufficient permissions

Notes:

For dealer users, all stats are scoped to their own customers only. online_now is fetched via RadiusService::getOnlineCount() which queries the radacct table for active sessions. monthly_revenue counts all payments in the current calendar month.


CUSTOMERS

LIST CUSTOMERS

Method: GET URL: /api/mobile/customers Auth: Required (Bearer token) Rate Limit: Default (60/min)

Query Parameters:

Parameter Type Required Description
page integer No Page number (default: 1)
per_page integer No Results per page (default: 20, max: 100)
status string No Filter by status: active, expired, suspended, disabled, temp_extended
search string No Search by name, username, mobile
dealer_id integer No Filter by dealer (super_admin only)
package_id integer No Filter by package
area_id integer No Filter by area
expiring_days integer No Show customers expiring within N days

Response (200):

{
  "data": [
    {
      "id": 101,
      "name": "Muhammad Ali",
      "pppoe_username": "ali_01",
      "mobile": "03001234567",
      "status": "active",
      "expiry_date": "2026-08-13",
      "package_name": "10 Mbps Unlimited",
      "package_price": 2500,
      "dealer_name": "City Zone Dealer",
      "area_name": "Johar Town",
      "online": true,
      "days_remaining": 31
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 20,
    "total": 1247,
    "last_page": 63
  }
}

Error Responses:

  • 401: Unauthenticated
  • 403: Forbidden

Notes:

Dealer users can only see their own customers (dealer_id = auth()->id()). The online field is a boolean from RadiusService::isOnline($pppoe_username). For large lists, avoid requesting online status as it adds latency — it is included only if the backend computes it efficiently via cached data.


CREATE CUSTOMER

Method: POST URL: /api/mobile/customers Auth: Required (Bearer token) — requires create_customers permission Rate Limit: Default (60/min)

Request Body:

Field Type Required Description
name string Yes Customer full name
pppoe_username string Yes PPPoE username (unique, alphanumeric + underscore)
pppoe_password string Yes PPPoE password (min 8 chars)
mobile string Yes Mobile number (Pakistani format preferred)
email string No Email address
address string No Physical address
cnic string No CNIC number
package_id integer Yes Package ID from /api/mobile/packages
dealer_id integer No Dealer ID (super_admin can assign; dealers auto-assigned)
area_id integer No Area ID
nas_id integer No NAS/router ID
expiry_date date No Initial expiry date (default: today + package days)
static_ip string No Static IP if required (must be valid IP)
auto_renew boolean No Auto-renew on expiry (default: false)

Example Request:

{
  "name": "Muhammad Ahmed",
  "pppoe_username": "ahmed_001",
  "pppoe_password": "SecurePass123",
  "mobile": "03211234567",
  "package_id": 5,
  "area_id": 3,
  "nas_id": 2
}

Response (201):

{
  "data": {
    "id": 502,
    "name": "Muhammad Ahmed",
    "pppoe_username": "ahmed_001",
    "status": "active",
    "expiry_date": "2026-08-13",
    "package_name": "10 Mbps Unlimited",
    "created_at": "2026-07-13T15:00:00Z"
  },
  "message": "Customer created successfully"
}

Error Responses:

  • 401: Unauthenticated
  • 403: No create_customers permission
  • 422: Validation error (e.g., username already taken)

Notes:

After creation, RadiusService::syncCustomer() is called automatically to add the customer to FreeRADIUS tables (radcheck, radusergroup, radgroupreply). An optional WhatsApp welcome message may be sent if configured.


SEARCH CUSTOMERS

Method: GET URL: /api/mobile/customers/search Auth: Required (Bearer token) Rate Limit: Default (60/min)

Query Parameters:

Parameter Type Required Description
q string Yes Search query (min 2 chars)
limit integer No Max results to return (default: 10, max: 50)

Response (200):

{
  "data": [
    {
      "id": 101,
      "name": "Muhammad Ali",
      "pppoe_username": "ali_01",
      "mobile": "03001234567",
      "status": "active",
      "expiry_date": "2026-08-13"
    }
  ]
}

Notes:

Searches across name, pppoe_username, mobile, and email fields. Uses ILIKE (case-insensitive). Results are scoped to the authenticated user's accessible customers. Useful for quick customer lookup in mobile forms.


GET CUSTOMER DETAIL

Method: GET URL: /api/mobile/customers/{id} Auth: Required (Bearer token) Rate Limit: Default (60/min)

URL Parameters:

Parameter Type Required Description
id integer Yes Customer ID

Response (200):

{
  "data": {
    "id": 101,
    "name": "Muhammad Ali",
    "pppoe_username": "ali_01",
    "pppoe_password": "hidden",
    "mobile": "03001234567",
    "email": "ali@example.com",
    "address": "House 5, Street 3, Johar Town",
    "cnic": "35202-1234567-1",
    "status": "active",
    "expiry_date": "2026-08-13",
    "days_remaining": 31,
    "package": {
      "id": 5,
      "name": "10 Mbps Unlimited",
      "price": 2500,
      "speed_download": 10240,
      "speed_upload": 5120,
      "validity_days": 30
    },
    "dealer": {
      "id": 3,
      "name": "City Zone Dealer"
    },
    "area": {
      "id": 7,
      "name": "Johar Town",
      "city": "Lahore"
    },
    "nas": {
      "id": 2,
      "name": "JT-Router-01",
      "ip": "192.168.1.1"
    },
    "static_ip": null,
    "online": true,
    "last_online": "2026-07-13T14:45:00Z",
    "billing_summary": {
      "total_paid": 30000,
      "last_payment": 2500,
      "last_payment_date": "2026-07-01"
    },
    "created_at": "2025-01-15T10:00:00Z",
    "updated_at": "2026-07-01T09:00:00Z"
  }
}

Error Responses:

  • 401: Unauthenticated
  • 403: Customer belongs to another dealer
  • 404: Customer not found

RECHARGE CUSTOMER

Method: POST URL: /api/mobile/customers/{id}/recharge Auth: Required (Bearer token) — requires recharge_customers permission Rate Limit: 10 requests/minute (throttled — destructive financial operation)

URL Parameters:

Parameter Type Required Description
id integer Yes Customer ID

Request Body:

Field Type Required Description
package_id integer No Package to apply (defaults to current package)
amount number Yes Payment amount in PKR
payment_method string No cash, bank_transfer, jazzcash, easypaisa (default: cash)
months integer No Number of months to extend (default: 1)
notes string No Transaction notes
send_whatsapp boolean No Send WhatsApp receipt (default: true if configured)

Example Request:

{
  "amount": 2500,
  "payment_method": "cash",
  "months": 1,
  "send_whatsapp": true
}

Response (200):

{
  "data": {
    "invoice_id": 8821,
    "customer_id": 101,
    "customer_name": "Muhammad Ali",
    "amount_paid": 2500,
    "payment_method": "cash",
    "new_expiry_date": "2026-08-13",
    "status": "active",
    "whatsapp_sent": true,
    "created_at": "2026-07-13T15:30:00Z"
  },
  "message": "Customer recharged successfully"
}

Error Responses:

  • 401: Unauthenticated
  • 403: No recharge_customers permission
  • 404: Customer not found
  • 422: Validation error (e.g., amount must be positive)
  • 429: Rate limit exceeded (max 10 recharges/min)

Notes:

Internally calls BillingService::generateInvoice() + BillingService::recordPayment() + BillingService::renew() + RadiusService::syncCustomer(). If the customer was expired/suspended, their RADIUS account is re-enabled immediately. A WhatsApp receipt is sent if send_whatsapp is true and WhatsApp is configured. All transactions are recorded in invoices, payments, and ledger_entries tables.


EXTEND SERVICE

Method: POST URL: /api/mobile/customers/{id}/extend Auth: Required (Bearer token) — requires extend_customers permission Rate Limit: Default (60/min)

Request Body:

Field Type Required Description
days integer Yes Number of days to extend
reason string No Reason for free extension (for audit log)

Example Request:

{
  "days": 3,
  "reason": "Service outage compensation"
}

Response (200):

{
  "data": {
    "customer_id": 101,
    "previous_expiry": "2026-08-13",
    "new_expiry_date": "2026-08-16",
    "days_added": 3,
    "reason": "Service outage compensation"
  },
  "message": "Service extended successfully"
}

Error Responses:

  • 401: Unauthenticated
  • 403: No extend_customers permission
  • 404: Customer not found
  • 422: Days must be a positive integer

Notes:

This extends service without any payment. It creates a temp_extension record in the audit log. Used for compensating customers for outages or promotional extensions. Does not create an invoice or payment record. Triggers RadiusService::syncCustomer() to update the session.


TOGGLE CUSTOMER STATUS

Method: POST URL: /api/mobile/customers/{id}/toggle Auth: Required (Bearer token) — requires toggle_customers permission Rate Limit: Default (60/min)

Request Body:

Field Type Required Description
action string Yes enable or disable
reason string No Reason for the action

Example Request:

{
  "action": "disable",
  "reason": "Customer requested temporary suspension"
}

Response (200):

{
  "data": {
    "customer_id": 101,
    "previous_status": "active",
    "new_status": "disabled",
    "action": "disable"
  },
  "message": "Customer disabled successfully"
}

Error Responses:

  • 401: Unauthenticated
  • 403: No toggle_customers permission
  • 404: Customer not found
  • 422: Invalid action value

Notes:

disable sets status to disabled and removes the customer from RADIUS (they cannot connect). enable sets status back to active (if expiry is in the future) and re-syncs RADIUS. This is a hard enable/disable — different from suspension (which is automatic on expiry).


CHANGE CUSTOMER PACKAGE

Method: POST URL: /api/mobile/customers/{id}/package Auth: Required (Bearer token) — requires edit_customers permission Rate Limit: Default (60/min)

Request Body:

Field Type Required Description
package_id integer Yes New package ID
immediate boolean No Apply immediately (true) or on next renewal (false, default)

Example Request:

{
  "package_id": 8,
  "immediate": true
}

Response (200):

{
  "data": {
    "customer_id": 101,
    "previous_package": {
      "id": 5,
      "name": "10 Mbps Unlimited"
    },
    "new_package": {
      "id": 8,
      "name": "20 Mbps Unlimited"
    },
    "applied": "immediate"
  },
  "message": "Package changed successfully"
}

Error Responses:

  • 401: Unauthenticated
  • 403: No edit_customers permission
  • 404: Customer or package not found
  • 422: Package not available or validation error

Notes:

If immediate: true, RadiusService::syncCustomer() is called immediately to apply new speed limits. The CoA (Change of Authorization) request is sent to the NAS if the customer is currently online, forcing their session to pick up new speed limits without disconnect (if NAS supports CoA).


GET LIVE TRAFFIC

Method: GET URL: /api/mobile/customers/{id}/live-traffic Auth: Required (Bearer token) Rate Limit: Default (60/min)

URL Parameters:

Parameter Type Required Description
id integer Yes Customer ID

Response (200) — Online:

{
  "data": {
    "customer_id": 101,
    "pppoe_username": "ali_01",
    "online": true,
    "rx_bps": 4718592,
    "tx_bps": 1048576,
    "rx_human": "4.50 Mbps",
    "tx_human": "1.00 Mbps",
    "session_start": "2026-07-13T08:00:00Z",
    "session_duration_hours": 6.5,
    "session_rx_bytes": 1073741824,
    "session_tx_bytes": 268435456,
    "nas_ip": "192.168.1.1",
    "framed_ip": "10.20.30.40"
  }
}

Response (200) — Offline:

{
  "data": {
    "customer_id": 101,
    "pppoe_username": "ali_01",
    "online": false,
    "rx_bps": 0,
    "tx_bps": 0,
    "last_seen": "2026-07-13T10:30:00Z"
  }
}

Error Responses:

  • 401: Unauthenticated
  • 403: Customer not accessible
  • 404: Customer not found

Notes:

Calls MikroTikService::liveTraffic($pppoe_username) which connects to the MikroTik router via API and fetches real-time interface statistics. This is a live call — it may take 1-3 seconds. If the customer is offline or the MikroTik is unreachable, online: false is returned without an error. Do not call this endpoint more than once every 5 seconds per customer.


GET TRAFFIC HISTORY

Method: GET URL: /api/mobile/customers/{id}/traffic-history Auth: Required (Bearer token) Rate Limit: Default (60/min)

Query Parameters:

Parameter Type Required Description
period string No day, week, month (default: week)
from date No Start date (YYYY-MM-DD)
to date No End date (YYYY-MM-DD)

Response (200):

{
  "data": {
    "customer_id": 101,
    "period": "week",
    "from": "2026-07-07",
    "to": "2026-07-13",
    "total_rx_bytes": 10737418240,
    "total_tx_bytes": 2684354560,
    "total_rx_human": "10.00 GB",
    "total_tx_human": "2.50 GB",
    "timeline": [
      {
        "date": "2026-07-07",
        "rx_bytes": 1610612736,
        "tx_bytes": 402653184,
        "rx_human": "1.50 GB",
        "tx_human": "384 MB"
      }
    ],
    "sessions": [
      {
        "session_id": "abc123",
        "start": "2026-07-07T08:00:00Z",
        "stop": "2026-07-07T22:00:00Z",
        "duration_hours": 14,
        "rx_bytes": 1610612736,
        "tx_bytes": 402653184,
        "nas_ip": "192.168.1.1",
        "framed_ip": "10.20.30.40"
      }
    ]
  }
}

Error Responses:

  • 401: Unauthenticated
  • 404: Customer not found

Notes:

Data is sourced from the radacct table (FreeRADIUS accounting). Historical data is available for sessions that have been closed (stop time recorded). Ongoing sessions may not appear until they terminate. Maximum date range is 90 days.


TICKETS

LIST TICKETS

Method: GET URL: /api/mobile/tickets Auth: Required (Bearer token) Rate Limit: Default (60/min)

Query Parameters:

Parameter Type Required Description
page integer No Page number
status string No open, in_progress, resolved, closed
priority string No low, medium, high, critical
customer_id integer No Filter by customer
assigned_to integer No Filter by assignee (admin only)

Response (200):

{
  "data": [
    {
      "id": 301,
      "title": "Internet not working",
      "status": "open",
      "priority": "high",
      "customer": {
        "id": 101,
        "name": "Muhammad Ali",
        "mobile": "03001234567"
      },
      "assigned_to": null,
      "created_at": "2026-07-13T10:00:00Z",
      "updated_at": "2026-07-13T10:00:00Z"
    }
  ],
  "meta": {
    "current_page": 1,
    "per_page": 20,
    "total": 8,
    "last_page": 1
  }
}

Error Responses:

  • 401: Unauthenticated

CREATE TICKET

Method: POST URL: /api/mobile/tickets Auth: Required (Bearer token) Rate Limit: Default (60/min)

Request Body:

Field Type Required Description
customer_id integer Yes Customer the ticket is for
title string Yes Ticket title (max 255 chars)
description string Yes Detailed description
priority string No low, medium, high, critical (default: medium)
category string No connectivity, billing, hardware, speed, other

Response (201):

{
  "data": {
    "id": 302,
    "title": "Internet not working",
    "status": "open",
    "priority": "high",
    "customer_id": 101,
    "created_at": "2026-07-13T15:00:00Z"
  },
  "message": "Ticket created successfully"
}

Error Responses:

  • 401: Unauthenticated
  • 403: No access to this customer
  • 422: Validation error

PACKAGES

LIST PACKAGES

Method: GET URL: /api/mobile/packages Auth: Required (Bearer token) Rate Limit: Default (60/min)

Query Parameters:

Parameter Type Required Description
active_only boolean No Only return active packages (default: true)

Response (200):

{
  "data": [
    {
      "id": 5,
      "name": "10 Mbps Unlimited",
      "price": 2500,
      "speed_download_kbps": 10240,
      "speed_upload_kbps": 5120,
      "speed_display": "10 Mbps / 5 Mbps",
      "validity_days": 30,
      "data_limit_gb": null,
      "is_unlimited": true,
      "radius_group": "10mbps-unlimited",
      "is_active": true
    }
  ]
}

Notes:

Returns all packages visible to the authenticated user. Used to populate package selector dropdowns in create/recharge/change-package forms. Dealers see only packages assigned to them if package restrictions are configured.


REPORTS

GET REPORTS DATA

Method: GET URL: /api/mobile/reports Auth: Required (Bearer token) — requires view_reports permission Rate Limit: Default (60/min)

Query Parameters:

Parameter Type Required Description
type string Yes revenue, customers, expiry, collections
from date No Start date (YYYY-MM-DD)
to date No End date (YYYY-MM-DD)
dealer_id integer No Filter by dealer (super_admin only)
group_by string No day, week, month

Response (200) — type=revenue:

{
  "data": {
    "type": "revenue",
    "from": "2026-07-01",
    "to": "2026-07-13",
    "total": 1625000,
    "currency": "PKR",
    "breakdown": [
      {
        "date": "2026-07-01",
        "amount": 125000,
        "transactions": 50
      }
    ]
  }
}

Error Responses:

  • 401: Unauthenticated
  • 403: No view_reports permission
  • 422: Invalid report type or date range

Notes:

For dealer users, reports are automatically scoped to their own customers only. Maximum date range is 365 days. For large date ranges, consider using group_by: month to reduce response size.


NETWORK

GET NETWORK STATUS

Method: GET URL: /api/mobile/network Auth: Required (Bearer token) — requires view_network permission Rate Limit: Default (60/min)

Response (200):

{
  "data": {
    "nas_list": [
      {
        "id": 1,
        "name": "JT-Router-01",
        "ip": "192.168.1.1",
        "type": "mikrotik",
        "status": "online",
        "online_users": 145,
        "total_users": 200,
        "uptime": "45d 12h 30m",
        "cpu_load": 23,
        "memory_used_mb": 512,
        "memory_total_mb": 2048
      }
    ],
    "olt_overview": [
      {
        "id": 1,
        "name": "Huawei OLT-01",
        "ip": "192.168.2.1",
        "type": "huawei",
        "status": "online",
        "total_onus": 128,
        "online_onus": 115,
        "alarm_count": 2
      }
    ],
    "summary": {
      "total_nas": 5,
      "online_nas": 5,
      "offline_nas": 0,
      "total_online_users": 312,
      "network_health": "healthy"
    }
  }
}

Error Responses:

  • 401: Unauthenticated
  • 403: No view_network permission

Notes:

NAS status is fetched via MikroTikService::getStatus() for each MikroTik device. OLT data is from the olt_devices table with last-polled status. The network_health field is computed: healthy if all NAS online, degraded if some offline, critical if majority offline. This endpoint may take 3-10 seconds if live NAS polling is enabled.


Mobile API vs Web Interface Differences

Aspect Mobile API Web Interface
Authentication Sanctum Bearer token Laravel session + CSRF
Response format JSON Inertia.js (JSON + HTML)
Rate limiting Enforced on destructive ops Standard session limits
Permissions Same enforcement Same enforcement
Business logic Same services Same services
Real-time Polling-based Inertia partial reload
File uploads multipart/form-data multipart/form-data

The same BillingService, RadiusService, MikroTikService, and WhatsAppService classes are used by both the web and mobile APIs. Business rules are identical — the API is not a shortcut around permissions.


Testing the API

Step 1: Login and Get Token

curl -X POST https://pyroradius.pyronet.com.pk/api/mobile/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "admin@pyronet.com.pk",
    "password": "change-me-on-first-login",
    "device_name": "curl-test"
  }'

Copy the token value from the response.

Step 2: Use the Token

# Set the token as a variable
TOKEN="1|your_token_here"

# Get dashboard stats
curl -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  https://pyroradius.pyronet.com.pk/api/mobile/dashboard

# List customers
curl -H "Authorization: Bearer $TOKEN" \
  "https://pyroradius.pyronet.com.pk/api/mobile/customers?status=active&per_page=10"

# Get customer detail
curl -H "Authorization: Bearer $TOKEN" \
  https://pyroradius.pyronet.com.pk/api/mobile/customers/101

# Recharge a customer
curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"amount": 2500, "payment_method": "cash"}' \
  https://pyroradius.pyronet.com.pk/api/mobile/customers/101/recharge

Step 3: Logout

curl -X POST \
  -H "Authorization: Bearer $TOKEN" \
  https://pyroradius.pyronet.com.pk/api/mobile/logout

Using Postman

  1. Create a new collection: "PyroRadius Mobile API"
  2. Set base URL variable: {{base_url}} = https://pyroradius.pyronet.com.pk/api/mobile
  3. Add a collection variable {{token}} (start empty)
  4. Add a pre-request script to the Login request to automatically save the token:
    pm.test("Login successful", function() {
      var json = pm.response.json();
      pm.collectionVariables.set("token", json.token);
    });
    
  5. In Authorization tab of other requests, use: Bearer {{token}}

Error Handling Best Practices

For Mobile App Developers

  1. Always check HTTP status code first — do not rely solely on response body parsing.
  2. Handle 401 globally — redirect to login screen and clear stored token.
  3. Handle 429 with backoff — wait Retry-After header seconds before retrying.
  4. Handle 422 per-field — map errors.field_name to form field validation messages.
  5. Handle 500 gracefully — show "Server error, please try again" — do not retry automatically.
  6. Token storage — use Android Keystore / iOS Secure Enclave, never SharedPreferences/UserDefaults in plaintext.

Last updated: 2026-07-13 | Maintained by: PyroNet Solutions Dev Team