Skip to main content

Native app integration

The official companion apps for ApexOS are ApexConnect for iOS and Android. They integrate with the core through the mobile_app component, which provides a registration endpoint, a per-device webhook channel, sensor entities, device tracking, and push notifications. Any native app can implement the same contract.

Registration

An app first obtains tokens via the auth flow, then registers the device:

POST /api/mobile_app/registrations
Authorization: Bearer <ACCESS_TOKEN>

Request fields:

FieldRequiredNotes
app_idyesApplication identifier
app_nameyesHuman-readable app name
app_versionyes
device_nameyesFalls back to device_id if it slugifies to empty
manufactureryes
modelyes
os_nameyes
os_versionno
device_idno
supports_encryptionno (default false)Request an encryption key
app_datanoPush configuration, see below

Unknown extra fields are stripped, so newer apps can safely send more data. Response (201 Created):

{
"cloudhook_url": null,
"remote_ui_url": null,
"secret": "d5e...hex",
"webhook_id": "abcd1234..."
}
  • webhook_id — a randomly generated secret identifier; all subsequent app→core traffic is POSTed to /api/webhook/{webhook_id} without any Authorization header (the webhook ID is the credential; keep it secret and prefer HTTPS).
  • secret — returned only when supports_encryption was true: a hex key for NaCl SecretBox payload encryption.
  • cloudhook_url / remote_ui_url — populated only when an active cloud subscription is connected and the user is not local-only.

Registration triggers a config-entry flow that creates the device and its entities.

Webhook channel

Every message is a POST to /api/webhook/{webhook_id} with an envelope:

{"type": "<action>", "data": { }}

or, when encryption is enabled:

{"type": "<action>", "encrypted": true, "encrypted_data": "<base-encoded ciphertext>"}

Once a registration declares supports_encryption, plaintext webhooks are rejected with an encryption_required error. Responses to encrypted registrations are wrapped the same way ({"encrypted": true, "encrypted_data": ...}). A webhook for a deleted registration returns HTTP 410 Gone. Invalid payloads are deliberately answered with an empty-OK response so probes learn nothing.

Webhook actions

typePayloadResult
update_registrationapp_version, device_name, manufacturer, model, optional os_version, app_dataUpdated (sanitized) registration
get_configInstance config: latitude, longitude, elevation, apex_device_id, unit_system, location_name, time_zone, components, version, theme_color, entity enable/disable map, cloud URLs when available
call_servicedomain, service, optional service_dataExecutes the service
fire_eventevent_type, optional event_dataFires a bus event
render_templatemap of key → {template, variables}Map of key → rendered result (per-key errors reported inline)
update_locationoptional location_name, gps (+ required gps_accuracy when gps present), battery, speed, altitude, course, vertical_accuracy, in_zonesUpdates the device tracker
register_sensorsee belowCreates/updates a sensor entity (201)
update_sensor_stateslist of {type, unique_id, state, attributes?, icon?}Batch state updates; per-sensor errors (not_registered, invalid_format)
enable_encryptionReturns a fresh secret; fails with encryption_already_enabled if already on
stream_cameracamera_entity_idMJPEG (and HLS when available) stream paths
conversation_processtext, optional language, conversation_idRuns the text through the conversation pipeline (see Intents)
get_zonesAll zone entities
scan_tagtag_idFires a tag-scanned event for this device

Sensor registration

register_sensor fields:

  • type (required) — sensor or binary_sensor
  • unique_id (required) — unique per device; the entity's stored unique ID is namespaced with the webhook ID
  • name (required) — displayed as "<device name> <sensor name>"
  • statenull, bool, int, float, or string (default null)
  • optional: attributes, device_class, unit_of_measurement, state_class, entity_category, icon (default mdi:cellphone), disabled

Re-registering an existing unique_id updates the entity registry entry (name, device class, unit, category, icon, disabled state) and applies the state — so registration is idempotent and also usable for reconfiguration.

Push notifications

Notifications flow core → app through a notify service created per registration. How they are delivered is controlled by the app_data object the app sends at registration (and can change later via update_registration):

{
"push_token": "<platform push token>",
"push_url": "https://<push relay>/api/notify",
"push_websocket_channel": true
}
  • push_token and push_url must be sent together (push_cloud group). When set, the core POSTs notification payloads to the app-provided push URL, forwarding the push token — the app vendor operates that relay endpoint.
  • The relay's response may include rate-limit info under rateLimits (successful, maximum, errors, resetsAt), which the core logs and enforces (a rate_limit_exceeded_sending_notification error is raised when exceeded).
  • push_websocket_channel: true enables a local push channel over the WebSocket API instead: the app subscribes with mobile_app/push_notification_channel (optionally support_confirm: true) and acknowledges deliveries with mobile_app/push_notification_confirm.
  • iOS live activities are supported via start_live_activity_token in app_data.

A clear_notification message with a matching tag removes a previously delivered notification.

Companion-app WebSocket usage

Beyond the webhook channel, the apps use the regular WebSocket API with their long-lived session: subscribe_events, call_service, get_states, the auth commands, and the mobile_app/push_notification_channel subscription described above.