towerops/MOBILE_API.md

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