Section 3 — API Reference
Tenant API Manual
REST endpoints, authentication, and real-time channels for building custom integrations on top of a tenant's data.
Overview
The Tenant Portal API is a JSON REST API served by the central platform at https://api.your-domain.com/api/portal/. It gives authorised tenant portal applications machine-level access to devices, incidents, beats, and assignees — all automatically scoped to the tenant.
This API is consumed internally by the server-tenant container but can also be used by custom dashboards, mobile apps, or backend integrations built for a specific tenant.
Authentication
Every request requires two headers:
Authorization: Bearer {APP_TENANT_API_KEY}
X-Tenant-Id: {APP_TENANT_ID}- APP_TENANT_API_KEY — the machine API key issued when the tenant was approved. Shown once in the admin panel under Portal API Keys. Stored hashed server-side.
- APP_TENANT_ID — the tenant's numeric database ID, visible in the admin panel.
Both headers are validated on every request. The tenant context is set from the API key — all queries are automatically scoped to that tenant's data. There is no cross-tenant data access possible.
Optional: user attribution
To attribute actions to a specific user (for audit trails), include the user's Passport access token:
X-User-Token: {user_passport_access_token}Base URL
https://api.your-domain.com/api/portal/
Endpoints
Devices
/devicesList all devices for the tenant. Supports ?search= and ?status= filters. Paginated (50 per page).
/devices/{id}Get a single device with device type, current assignment, and beat.
/devices/{id}/signalsGet the latest signal snapshot (lat, lon, speed, battery, timestamp).
Incidents
/incidentsList incidents. Supports ?status= and ?device_id= filters. Paginated (50 per page).
/incidents/{id}Get a single incident with device, assignee, and beat details.
/incidentsCreate a new incident manually.
/incidents/{id}Update incident status (open, acknowledged, escalated, resolved) and resolution notes.
Beats
/beatsList all top-level beats (inclusion and exclusion zones) with children and supervisor.
/beats/{id}Get a single beat with assigned devices.
Assignees
/assigneesList all assignees with type and current device assignment.
/assignees/{id}Get a single assignee with beat assignments.
Real-time (WebSocket auth)
/broadcasting/authSign a Pusher/Soketi private channel auth response. Required for real-time device location updates.
Response Format
All endpoints return JSON. Lists return paginated objects:
{
"data": [...],
"total": 142,
"current_page": 1,
"last_page": 3,
"per_page": 50
}Single resources return the object directly. Errors follow Laravel's standard JSON error format with message and optional errors keys.
Real-time — Soketi WebSocket
Device positions stream in real time via Soketi (Pusher-compatible WebSocket). Subscribe to the tenant's private channel:
// Channel name
private-tenant.{TENANT_ID}.locations
// Event fired on position batch
App\Events\LocationsBatchEvent
// Payload
{
"positions": [
{
"device_id": 42,
"lat": 31.5204,
"lon": 74.3587,
"battery": 87,
"recorded_at": "2026-06-04T08:12:33Z"
}
]
}Channel auth is handled by the POST /broadcasting/auth endpoint using the machine API key — no user session required.
Rate Limits
- REST API: 200 requests per minute per API key
- WebSocket events: 200 client events per second per channel
Responses include standard rate-limit headers (X-RateLimit-Limit, X-RateLimit-Remaining).
Example — List Devices
curl -X GET \ https://api.your-domain.com/api/portal/devices \ -H "Authorization: Bearer tpk_xxxxxxxxxxxxxxxx" \ -H "X-Tenant-Id: 42" \ -H "Accept: application/json"
Example — Resolve an Incident
curl -X PATCH \
https://api.your-domain.com/api/portal/incidents/17 \
-H "Authorization: Bearer tpk_xxxxxxxxxxxxxxxx" \
-H "X-Tenant-Id: 42" \
-H "Content-Type: application/json" \
-d '{"status":"resolved","resolution_notes":"Operator confirmed safe return"}'