airplx.v1
Box ↔ server protocol, M2a v0.2. Generated from the server code.
01Connection
| URL | wss://device-staging.airplx.com/airplx/v1/device |
| Subprotocol | airplx.v1. If you offer several, the server picks the highest it supports, or closes 4002. |
| TLS | Server certificate only, for now. Mutual TLS comes later (M2c). |
| Keepalive | Server pings every 30 s. The box reconnects after 75 s of silence. |
| Offline | After 180 s without contact. |
| Limits | JSON ≤ 4,096 B, binary ≤ 32,520 B. Larger closes 1009. |
| Replacing | A new connection with the same device_id closes the old one with 4001. |
| State | In memory. A deploy clears it and closes connections with 1001. |
Reconnect back-off
Seconds, with jitter. Retry-After honoured on 429 and 503.
02Session flow
The box resumes after max(welcome.ack, ack_watermark). The server stores duplicates once and acknowledges them.
03Playground
Uses a random device_id from 900000–999999.
04Versioning
| Subprotocol | Path | Spec | Binary layouts |
|---|---|---|---|
airplx.v1 | /airplx/v1/device | M2a v0.2 | TBD |
The version is in the path and the subprotocol. Binary layouts are fixed within a version, and new binary messages get new magics. JSON changes are additive, and unknown fields are ignored. The HTTP API is under /api/v1.
05Box → server
hello
First message on every connection.
This serverReplies with welcome. Anything else first, or a device_id outside 1–999999, closes 1002. Reply: welcome.
| Field | Type | Notes |
|---|---|---|
type | string | "hello" |
device_id | integer | 1–999999 |
boot_seq | TBD | |
ack_watermark | TBD | The box resumes after max(welcome.ack, ack_watermark). |
journal | TBD | |
caps | TBD | What the firmware supports. |
| Other fields: TBD | ||
{
"type": "hello",
"device_id": 123456
}upload
One 1 s audio record. Header fields: device_id, boot_seq, chunk_id, flags, monotonic_counter, utc_ms, time anchor, channel_mask, levels, audio_state, AGC, clips, payload_crc32, header_crc32.
This serverRecognised by magic and length, then logged. Not parsed or acknowledged until the header layout is known.
| Offset | Size | Field | Notes |
|---|---|---|---|
| 0 | 4 | magic | 0x414C5058 |
| 4 | 4 | frame_len | 32,512 |
| 8 | 512 | header v2 | Layout TBD (Appendix A.1). |
| 520 | 32,000 | payload | 8,000 frames × (L int16, R int16), 8 kHz, 16-bit PCM, uncompressed. R = 0 on the single-channel board. |
Little-endian. Bytes.
Example: TBD (Appendix C).
quiet
Header only, for a quiet second. Proposed. The Q-042 answer is heartbeats instead.
This serverRecognised and logged like upload.
| Offset | Size | Field | Notes |
|---|---|---|---|
| 0 | 4 | magic | 0x414C5058 |
| 4 | 4 | frame_len | 512 |
| 8 | 512 | header v2 | Layout TBD (Appendix A.1). |
Little-endian. Bytes.
Example: TBD (Appendix C).
TRQ1
Time request: seq, t1. Up to 8 after every welcome and every hour.
This serverLogged, not answered. Layout TBD.
16 B. Layout TBD (Appendix A.1).
Example: TBD (Appendix C).
status
Every 60 s. Groups: state, capture, upload, network, audio, health, storage, system, counters.
This serverStored as the box's last status. No reply.
| Field | Type | Notes |
|---|---|---|
type | string | "status" |
state | TBD | |
capture | TBD | |
upload | TBD | |
network | TBD | |
audio | TBD | |
health | TBD | |
storage | TBD | |
system | TBD | |
counters | TBD | |
| Other fields: TBD | ||
{
"type": "status"
}event
Events and alarms. De-duplicated by (device_id, boot_seq, seq).
This serverStored once per (boot_seq, seq). No reply.
| Field | Type | Notes |
|---|---|---|
type | string | "event" |
boot_seq | TBD | |
seq | TBD | |
| Other fields: TBD | ||
{
"type": "event"
}claim
Associates the box with an account and location. The claim token goes backend → app → box → server.
This serverReplies with claim_result, no fields yet. Reply: claim_result.
| Field | Type | Notes |
|---|---|---|
type | string | "claim" |
| Other fields: TBD | ||
{
"type": "claim"
}unclaim
The box left its account.
This serverLogged. No reply.
| Field | Type | Notes |
|---|---|---|
type | string | "unclaim" |
| Other fields: TBD | ||
{
"type": "unclaim"
}test_capture
Onboarding test. Its frames are flagged as a test and uploaded first.
This serverReplies with test_capture_result straight away, no fields yet. Reply: test_capture_result.
| Field | Type | Notes |
|---|---|---|
type | string | "test_capture" |
| Other fields: TBD | ||
{
"type": "test_capture"
}cmd_result
accepted (≤ 2 s), in_progress (long commands only), then completed or failed with a code.
This serverStored with the command. No reply.
| Field | Type | Notes |
|---|---|---|
type | string | "cmd_result" |
request_id | string | From cmd. |
state | string | accepted · in_progress · completed · failed |
code | string | "OK" or an error code. |
data | object |
{
"type": "cmd_result",
"request_id": "<uuid>",
"state": "completed",
"code": "OK",
"data": {}
}06Server → box
welcome
Reply to hello, within 2 s. Nothing is sent before it.
This serverack and config_rev are 0. pending_commands holds cmd messages queued while the box was offline.
| Field | Type | Notes |
|---|---|---|
type | string | "welcome" |
ack | TBD | |
config_rev | TBD | |
pending_commands | TBD | |
| Other fields: TBD | ||
{
"type": "welcome",
"ack": 0,
"config_rev": 0,
"pending_commands": []
}ACK1
Cumulative acknowledgement: every chunk_id up to it is stored.
This serverNot sent yet. Layout TBD.
12 B. Layout TBD (Appendix A.1).
Example: TBD (Appendix C).
TRS1
Time response: seq, t1, t2, t3. offset = ((t2 − t1) + (t3 − t4)) / 2.
This serverNot sent yet. Layout TBD.
32 B. Layout TBD (Appendix A.1).
Example: TBD (Appendix C).
claim_result
Result of claim. Refused if the box is associated elsewhere.
This serverSent with no fields yet.
| Field | Type | Notes |
|---|---|---|
type | string | "claim_result" |
| Other fields: TBD | ||
{
"type": "claim_result"
}test_capture_result
Sent once the test capture is stored.
This serverSent with no fields yet.
| Field | Type | Notes |
|---|---|---|
type | string | "test_capture_result" |
| Other fields: TBD | ||
{
"type": "test_capture_result"
}cmd
Remote command. Same names, args and codes as BLE (M2b §7). The same request_id again returns the stored result.
This serverSent from POST /api/v1/devices/{id}/cmd, or in the next welcome if the box is offline.
| Field | Type | Notes |
|---|---|---|
type | string | "cmd" |
request_id | string | uuid |
name | string | See Remote commands. |
args | object | Per command (M2b §7.2). |
timeout_ms | integer | 30000 in the deck's example. |
{
"type": "cmd",
"request_id": "<uuid>",
"name": "find_device",
"args": {},
"timeout_ms": 30000
}error
A message that could not be processed. Either side can send one.
This serverSent with no fields for bad JSON or an unknown type.
| Field | Type | Notes |
|---|---|---|
type | string | "error" |
| Other fields: TBD | ||
{
"type": "error"
}07Close and HTTP codes
| Close | Meaning |
|---|---|
1000 | normal (e.g. ACK-stall reconnect) |
1001 | server going away |
1002 | protocol error |
1008 | unknown binary message, device_id mismatch (not enforced until layouts are known) |
1009 | message too big |
4000 | device revoked |
4001 | replaced by a newer connection |
4002 | unsupported protocol version |
| HTTP | When | Box |
|---|---|---|
400 | malformed upgrade | back-off |
403 | certificate revoked or unknown | 1 attempt / 10 min |
404 | wrong path | back-off |
426 | WebSocket version | back-off |
429 | too many connects | waits Retry-After |
503 | maintenance, overload | waits Retry-After |
08Error codes
| Code | Meaning |
|---|---|
E_CONFIG_REV_CONFLICT | set_config with a stale expected_rev. Returns the current values. |
| Others | TBD |
09Remote commands
| Command | Remote |
|---|---|
find_device | yes |
get_config | yes |
get_log | yes |
ota_start | yes |
reboot | yes |
retry_upload | yes |
run_network_diagnostics | yes |
self_test | yes |
set_config | yes |
factory_reset | no, button at the box |
format_journal | no, button at the box |
remove_account | no, button at the box |
10HTTP API
Needs a bearer token. Schema: Swagger, openapi.json. No token needed for /api/v1/stats and /health.
/api/v1/devicesBoxes seen since the last deploycurl -s https://device-staging.airplx.com/api/v1/devices \
-H "Authorization: Bearer $DEVICE_GW_ADMIN_TOKEN"/api/v1/devices/123456One box, with its hello and last statuscurl -s https://device-staging.airplx.com/api/v1/devices/123456 \
-H "Authorization: Bearer $DEVICE_GW_ADMIN_TOKEN"/api/v1/devices/123456/messages?limit=50Recent messages, both directionscurl -s https://device-staging.airplx.com/api/v1/devices/123456/messages?limit=50 \
-H "Authorization: Bearer $DEVICE_GW_ADMIN_TOKEN"/api/v1/devices/123456/cmdSend a commandcurl -s -X POST https://device-staging.airplx.com/api/v1/devices/123456/cmd \
-H "Authorization: Bearer $DEVICE_GW_ADMIN_TOKEN" \
-H 'Content-Type: application/json' -d '{"name":"find_device"}'/api/v1/devices/123456/commandsCommands and their resultscurl -s https://device-staging.airplx.com/api/v1/devices/123456/commands \
-H "Authorization: Bearer $DEVICE_GW_ADMIN_TOKEN"