539 lines
12 KiB
Markdown
539 lines
12 KiB
Markdown
# Towerops Mobile API Documentation
|
|
|
|
Version: 1.0
|
|
Base URL: `https://your-towerops-instance.com/api/v1/mobile`
|
|
|
|
## Table of Contents
|
|
|
|
- [Authentication](#authentication)
|
|
- [QR Code Login Flow](#qr-code-login-flow)
|
|
- [Session Management](#session-management)
|
|
- [Data Endpoints](#data-endpoints)
|
|
- [Organizations](#organizations)
|
|
- [Sites](#sites)
|
|
- [Equipment](#equipment)
|
|
- [Alerts](#alerts)
|
|
- [Error Handling](#error-handling)
|
|
- [Rate Limiting](#rate-limiting)
|
|
|
|
## Authentication
|
|
|
|
The Towerops Mobile API uses bearer token authentication. All authenticated requests must include an `Authorization` header:
|
|
|
|
```
|
|
Authorization: Bearer <session_token>
|
|
```
|
|
|
|
### QR Code Login Flow
|
|
|
|
The recommended authentication method is QR code login, which allows users to authenticate without entering credentials in the mobile app.
|
|
|
|
#### Step 1: User Generates QR Code
|
|
|
|
User logs into the web interface at `/mobile/qr-login` and generates a QR code containing a temporary token.
|
|
|
|
#### Step 2: Verify QR Token
|
|
|
|
**Endpoint:** `POST /api/v1/mobile/auth/qr/verify`
|
|
|
|
Verifies that a scanned QR token is valid before proceeding with login.
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"token": "base64-encoded-token-from-qr-code"
|
|
}
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"valid": true,
|
|
"user_email": "user@example.com"
|
|
}
|
|
```
|
|
|
|
**Response (401 Unauthorized):**
|
|
```json
|
|
{
|
|
"valid": false,
|
|
"error": "Invalid or expired token"
|
|
}
|
|
```
|
|
|
|
#### Step 3: Complete QR Login
|
|
|
|
**Endpoint:** `POST /api/v1/mobile/auth/qr/complete`
|
|
|
|
Completes the QR login by creating a long-lived mobile session (90 days).
|
|
|
|
**Request Body:**
|
|
```json
|
|
{
|
|
"token": "base64-encoded-token-from-qr-code",
|
|
"device_name": "iPhone 15 Pro",
|
|
"device_os": "iOS 17.2",
|
|
"app_version": "1.0.0"
|
|
}
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"session_token": "long-lived-bearer-token",
|
|
"expires_at": "2026-04-15T19:44:25Z",
|
|
"user": {
|
|
"id": "user-uuid",
|
|
"email": "user@example.com"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (401 Unauthorized):**
|
|
```json
|
|
{
|
|
"error": "Invalid or expired token"
|
|
}
|
|
```
|
|
|
|
**Response (422 Unprocessable Entity):**
|
|
```json
|
|
{
|
|
"error": "Failed to create session",
|
|
"details": {
|
|
"device_name": ["can't be blank"]
|
|
}
|
|
}
|
|
```
|
|
|
|
**Notes:**
|
|
- QR tokens expire after 5 minutes
|
|
- QR tokens can only be used once
|
|
- Session tokens expire after 90 days
|
|
- Store the `session_token` securely in the device keychain/keystore
|
|
|
|
### Session Management
|
|
|
|
#### Get Current Session
|
|
|
|
**Endpoint:** `GET /api/v1/mobile/auth/session`
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <session_token>
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"id": "session-uuid",
|
|
"device_name": "iPhone 15 Pro",
|
|
"device_os": "iOS 17.2",
|
|
"app_version": "1.0.0",
|
|
"last_used_at": "2026-01-15T19:44:25Z",
|
|
"expires_at": "2026-04-15T19:44:25Z"
|
|
}
|
|
```
|
|
|
|
#### Revoke Session (Logout)
|
|
|
|
**Endpoint:** `DELETE /api/v1/mobile/auth/session`
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer <session_token>
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"success": true
|
|
}
|
|
```
|
|
|
|
## Data Endpoints
|
|
|
|
All data endpoints require authentication via the `Authorization: Bearer <token>` header.
|
|
|
|
### Organizations
|
|
|
|
#### List Organizations
|
|
|
|
**Endpoint:** `GET /api/v1/mobile/organizations`
|
|
|
|
Returns all organizations the authenticated user has access to.
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"organizations": [
|
|
{
|
|
"id": "org-uuid",
|
|
"name": "Acme Corp",
|
|
"sites_count": 3,
|
|
"equipment_count": 15,
|
|
"active_alerts_count": 2
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Sites
|
|
|
|
#### List Sites
|
|
|
|
**Endpoint:** `GET /api/v1/mobile/organizations/:organization_id/sites`
|
|
|
|
Returns all sites for an organization.
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"sites": [
|
|
{
|
|
"id": "site-uuid",
|
|
"name": "Main Office",
|
|
"location": "New York, NY",
|
|
"equipment_count": 5,
|
|
"equipment_down_count": 1
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Response (403 Forbidden):**
|
|
```json
|
|
{
|
|
"error": "Access denied to this organization"
|
|
}
|
|
```
|
|
|
|
### Equipment
|
|
|
|
#### List Equipment
|
|
|
|
**Endpoint:** `GET /api/v1/mobile/organizations/:organization_id/equipment`
|
|
|
|
Returns all equipment for an organization with current status.
|
|
|
|
**Query Parameters:**
|
|
- `site_id` (optional): Filter by specific site UUID
|
|
- `status` (optional): Filter by status (`up`, `down`, `unknown`)
|
|
|
|
**Example:**
|
|
```
|
|
GET /api/v1/mobile/organizations/org-123/equipment?status=down&site_id=site-456
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"equipment": [
|
|
{
|
|
"id": "equipment-uuid",
|
|
"name": "Core Router",
|
|
"ip_address": "192.168.1.1",
|
|
"site_name": "Main Office",
|
|
"status": "up",
|
|
"uptime": "15d 4h",
|
|
"last_seen_at": "2026-01-15T19:44:25Z"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
#### Get Equipment Details
|
|
|
|
**Endpoint:** `GET /api/v1/mobile/equipment/:id`
|
|
|
|
Returns detailed equipment information including interfaces and sensors.
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"id": "equipment-uuid",
|
|
"name": "Core Router",
|
|
"ip_address": "192.168.1.1",
|
|
"status": "up",
|
|
"uptime": "15d 4h",
|
|
"site": {
|
|
"id": "site-uuid",
|
|
"name": "Main Office"
|
|
},
|
|
"interfaces": [
|
|
{
|
|
"id": "interface-uuid",
|
|
"name": "GigabitEthernet0/0",
|
|
"alias": "WAN",
|
|
"status": "up",
|
|
"admin_status": "up",
|
|
"speed": 1000000000,
|
|
"mac_address": "00:1A:2B:3C:4D:5E"
|
|
}
|
|
],
|
|
"sensors": [
|
|
{
|
|
"id": "sensor-uuid",
|
|
"name": "CPU Usage",
|
|
"type": "cpu",
|
|
"unit": "%",
|
|
"current_value": 45.5,
|
|
"status": "ok"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Response (404 Not Found):**
|
|
```json
|
|
{
|
|
"error": "Equipment not found"
|
|
}
|
|
```
|
|
|
|
**Response (403 Forbidden):**
|
|
```json
|
|
{
|
|
"error": "Access denied to this equipment"
|
|
}
|
|
```
|
|
|
|
### Alerts
|
|
|
|
#### List Alerts
|
|
|
|
**Endpoint:** `GET /api/v1/mobile/organizations/:organization_id/alerts`
|
|
|
|
Returns alerts for an organization.
|
|
|
|
**Query Parameters:**
|
|
- `severity` (optional): Filter by severity (`critical`, `warning`, `info`)
|
|
- `status` (optional): Filter by status (`active`, `acknowledged`, `resolved`)
|
|
- `limit` (optional): Number of alerts to return (default 50, max 200)
|
|
|
|
**Example:**
|
|
```
|
|
GET /api/v1/mobile/organizations/org-123/alerts?severity=critical&status=active&limit=100
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"alerts": [
|
|
{
|
|
"id": "alert-uuid",
|
|
"severity": "critical",
|
|
"status": "active",
|
|
"message": "Equipment Down: Core Router",
|
|
"equipment_name": "Core Router",
|
|
"equipment_id": "equipment-uuid",
|
|
"occurred_at": "2026-01-15T19:44:25Z"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Alert Severity Levels:**
|
|
- `critical`: Requires immediate attention (equipment down, critical sensor thresholds)
|
|
- `warning`: Requires attention (sensor warnings, interface changes)
|
|
- `info`: Informational (equipment up, sensor recovery)
|
|
|
|
**Alert Status Values:**
|
|
- `active`: Alert is active and unacknowledged
|
|
- `acknowledged`: Alert has been acknowledged but not resolved
|
|
- `resolved`: Alert has been resolved
|
|
|
|
## Error Handling
|
|
|
|
All API endpoints follow consistent error response formats.
|
|
|
|
### HTTP Status Codes
|
|
|
|
- `200 OK`: Request succeeded
|
|
- `400 Bad Request`: Invalid request parameters
|
|
- `401 Unauthorized`: Missing or invalid authentication token
|
|
- `403 Forbidden`: Authenticated but not authorized for this resource
|
|
- `404 Not Found`: Resource not found
|
|
- `422 Unprocessable Entity`: Validation errors
|
|
- `500 Internal Server Error`: Server error
|
|
|
|
### Error Response Format
|
|
|
|
```json
|
|
{
|
|
"error": "Human-readable error message"
|
|
}
|
|
```
|
|
|
|
For validation errors (422):
|
|
```json
|
|
{
|
|
"error": "Failed to create session",
|
|
"details": {
|
|
"field_name": ["error message"]
|
|
}
|
|
}
|
|
```
|
|
|
|
## Rate Limiting
|
|
|
|
The API does not currently implement rate limiting, but it may be added in future versions. Implement exponential backoff in your client for failed requests.
|
|
|
|
## Best Practices
|
|
|
|
### Token Storage
|
|
|
|
- Store session tokens securely in the device keychain (iOS) or keystore (Android)
|
|
- Never log or expose tokens in plaintext
|
|
- Clear tokens on logout
|
|
|
|
### Session Management
|
|
|
|
- Check session expiration before making requests
|
|
- Implement automatic token refresh or re-authentication when sessions expire
|
|
- Handle 401 responses by prompting for re-authentication
|
|
|
|
### Network Requests
|
|
|
|
- Implement timeout handling (30 seconds recommended)
|
|
- Use exponential backoff for retries
|
|
- Cache responses appropriately (organizations list, equipment details)
|
|
- Update last_used_at is automatic - no need to ping
|
|
|
|
### Error Handling
|
|
|
|
- Display user-friendly error messages
|
|
- Log errors for debugging
|
|
- Handle network connectivity issues gracefully
|
|
- Implement offline mode where appropriate
|
|
|
|
## Example Client Implementation
|
|
|
|
### Swift (iOS)
|
|
|
|
```swift
|
|
import Foundation
|
|
|
|
class ToweropsAPI {
|
|
private let baseURL = "https://your-instance.com/api/v1/mobile"
|
|
private var sessionToken: String?
|
|
|
|
// Authenticate with QR token
|
|
func completeQRLogin(token: String, deviceInfo: DeviceInfo) async throws -> SessionToken {
|
|
let url = URL(string: "\(baseURL)/auth/qr/complete")!
|
|
var request = URLRequest(url: url)
|
|
request.httpMethod = "POST"
|
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
|
|
let body = [
|
|
"token": token,
|
|
"device_name": deviceInfo.name,
|
|
"device_os": deviceInfo.os,
|
|
"app_version": deviceInfo.appVersion
|
|
]
|
|
request.httpBody = try JSONEncoder().encode(body)
|
|
|
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
|
|
guard let httpResponse = response as? HTTPURLResponse,
|
|
httpResponse.statusCode == 200 else {
|
|
throw APIError.authenticationFailed
|
|
}
|
|
|
|
let result = try JSONDecoder().decode(SessionToken.self, from: data)
|
|
self.sessionToken = result.session_token
|
|
return result
|
|
}
|
|
|
|
// Fetch organizations
|
|
func fetchOrganizations() async throws -> [Organization] {
|
|
guard let token = sessionToken else {
|
|
throw APIError.notAuthenticated
|
|
}
|
|
|
|
let url = URL(string: "\(baseURL)/organizations")!
|
|
var request = URLRequest(url: url)
|
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
|
|
let (data, response) = try await URLSession.shared.data(for: request)
|
|
|
|
guard let httpResponse = response as? HTTPURLResponse,
|
|
httpResponse.statusCode == 200 else {
|
|
throw APIError.requestFailed
|
|
}
|
|
|
|
let result = try JSONDecoder().decode(OrganizationsResponse.self, from: data)
|
|
return result.organizations
|
|
}
|
|
}
|
|
```
|
|
|
|
### Kotlin (Android)
|
|
|
|
```kotlin
|
|
import okhttp3.*
|
|
import kotlinx.serialization.json.Json
|
|
|
|
class ToweropsAPI(private val baseURL: String = "https://your-instance.com/api/v1/mobile") {
|
|
private val client = OkHttpClient()
|
|
private var sessionToken: String? = null
|
|
|
|
// Authenticate with QR token
|
|
suspend fun completeQRLogin(token: String, deviceInfo: DeviceInfo): SessionToken {
|
|
val json = """
|
|
{
|
|
"token": "$token",
|
|
"device_name": "${deviceInfo.name}",
|
|
"device_os": "${deviceInfo.os}",
|
|
"app_version": "${deviceInfo.appVersion}"
|
|
}
|
|
""".trimIndent()
|
|
|
|
val request = Request.Builder()
|
|
.url("$baseURL/auth/qr/complete")
|
|
.post(RequestBody.create(MediaType.parse("application/json"), json))
|
|
.build()
|
|
|
|
val response = client.newCall(request).execute()
|
|
if (!response.isSuccessful) {
|
|
throw APIException("Authentication failed")
|
|
}
|
|
|
|
val result = Json.decodeFromString<SessionToken>(response.body()!!.string())
|
|
sessionToken = result.session_token
|
|
return result
|
|
}
|
|
|
|
// Fetch organizations
|
|
suspend fun fetchOrganizations(): List<Organization> {
|
|
val token = sessionToken ?: throw APIException("Not authenticated")
|
|
|
|
val request = Request.Builder()
|
|
.url("$baseURL/organizations")
|
|
.header("Authorization", "Bearer $token")
|
|
.build()
|
|
|
|
val response = client.newCall(request).execute()
|
|
if (!response.isSuccessful) {
|
|
throw APIException("Request failed")
|
|
}
|
|
|
|
val result = Json.decodeFromString<OrganizationsResponse>(response.body()!!.string())
|
|
return result.organizations
|
|
}
|
|
}
|
|
```
|
|
|
|
## Support
|
|
|
|
For API support, bug reports, or feature requests:
|
|
- GitHub Issues: https://github.com/yourusername/towerops
|
|
- Documentation: https://docs.towerops.net
|
|
|
|
## Changelog
|
|
|
|
### Version 1.0 (2026-01-15)
|
|
- Initial release
|
|
- QR code authentication
|
|
- Basic data endpoints (organizations, sites, equipment, alerts)
|
|
- Session management
|