AirPlx Device Gateway API status airplx.v1 · M2a v0.2

airplx.v1

Box ↔ server protocol, M2a v0.2. Generated from the server code.

01Connection

URLwss://device-staging.airplx.com/airplx/v1/device
Subprotocolairplx.v1. If you offer several, the server picks the highest it supports, or closes 4002.
TLSServer certificate only, for now. Mutual TLS comes later (M2c).
KeepaliveServer pings every 30 s. The box reconnects after 75 s of silence.
OfflineAfter 180 s without contact.
LimitsJSON ≤ 4,096 B, binary ≤ 32,520 B. Larger closes 1009.
ReplacingA new connection with the same device_id closes the old one with 4001.
StateIn memory. A deploy clears it and closes connections with 1001.

Reconnect back-off

1
2
4
8
16
32
60
60

Seconds, with jitter. Retry-After honoured on 429 and 503.

02Session flow

BoxServerTLS + WebSocket upgrade, subprotocol airplx.v1hello {device_id, boot_seq, ack_watermark, journal, …}welcome {ack, config_rev, pending_commands} within 2 sTRQ1 × up to 8TRS1 × up to 8upload frames from the first record after ackACK1 after every framestatus every 60 s

The box resumes after max(welcome.ack, ack_watermark). The server stores duplicates once and acknowledges them.

03Playground

offline

Uses a random device_id from 900000–999999.

04Versioning

SubprotocolPathSpecBinary layouts
airplx.v1/airplx/v1/deviceM2a v0.2TBD

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

box → serverjson≤ 1,024 BM2a §5

First message on every connection.

This serverReplies with welcome. Anything else first, or a device_id outside 1–999999, closes 1002. Reply: welcome.

FieldTypeNotes
typestring"hello"
device_idinteger1–999999
boot_seqTBD
ack_watermarkTBDThe box resumes after max(welcome.ack, ack_watermark).
journalTBD
capsTBDWhat the firmware supports.
Other fields: TBD
{
  "type": "hello",
  "device_id": 123456
}

upload

box → serverbinary32,520 BM2a §7, A.1

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.

OffsetSizeFieldNotes
04magic0x414C5058
44frame_len32,512
8512header v2Layout TBD (Appendix A.1).
52032,000payload8,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

box → serverbinary520 BM2a §9

Header only, for a quiet second. Proposed. The Q-042 answer is heartbeats instead.

This serverRecognised and logged like upload.

OffsetSizeFieldNotes
04magic0x414C5058
44frame_len512
8512header v2Layout TBD (Appendix A.1).

Little-endian. Bytes.

Example: TBD (Appendix C).

TRQ1

box → serverbinary16 BM2a §15

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

box → serverjson≤ 2,048 BM2a §10

Every 60 s. Groups: state, capture, upload, network, audio, health, storage, system, counters.

This serverStored as the box's last status. No reply.

FieldTypeNotes
typestring"status"
stateTBD
captureTBD
uploadTBD
networkTBD
audioTBD
healthTBD
storageTBD
systemTBD
countersTBD
Other fields: TBD
{
  "type": "status"
}

event

box → serverjson≤ 1,024 BM2a §12

Events and alarms. De-duplicated by (device_id, boot_seq, seq).

This serverStored once per (boot_seq, seq). No reply.

FieldTypeNotes
typestring"event"
boot_seqTBD
seqTBD
Other fields: TBD
{
  "type": "event"
}

claim

box → serverjson≤ 1,024 BM2a §14

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.

FieldTypeNotes
typestring"claim"
Other fields: TBD
{
  "type": "claim"
}

unclaim

box → serverjson≤ 1,024 BM2a §14

The box left its account.

This serverLogged. No reply.

FieldTypeNotes
typestring"unclaim"
Other fields: TBD
{
  "type": "unclaim"
}

test_capture

box → serverjson≤ 1,024 BM2a §14

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.

FieldTypeNotes
typestring"test_capture"
Other fields: TBD
{
  "type": "test_capture"
}

cmd_result

box → serverjson≤ 4,096 BM2a §11

accepted (≤ 2 s), in_progress (long commands only), then completed or failed with a code.

This serverStored with the command. No reply.

FieldTypeNotes
typestring"cmd_result"
request_idstringFrom cmd.
statestringaccepted · in_progress · completed · failed
codestring"OK" or an error code.
dataobject
{
  "type": "cmd_result",
  "request_id": "<uuid>",
  "state": "completed",
  "code": "OK",
  "data": {}
}

06Server → box

welcome

server → boxjson≤ 1,024 BM2a §5

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.

FieldTypeNotes
typestring"welcome"
ackTBD
config_revTBD
pending_commandsTBD
Other fields: TBD
{
  "type": "welcome",
  "ack": 0,
  "config_rev": 0,
  "pending_commands": []
}

ACK1

server → boxbinary12 BM2a §8

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

server → boxbinary32 BM2a §15

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

server → boxjson≤ 1,024 BM2a §14

Result of claim. Refused if the box is associated elsewhere.

This serverSent with no fields yet.

FieldTypeNotes
typestring"claim_result"
Other fields: TBD
{
  "type": "claim_result"
}

test_capture_result

server → boxjson≤ 1,024 BM2a §14

Sent once the test capture is stored.

This serverSent with no fields yet.

FieldTypeNotes
typestring"test_capture_result"
Other fields: TBD
{
  "type": "test_capture_result"
}

cmd

server → boxjson≤ 4,096 BM2a §11

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.

FieldTypeNotes
typestring"cmd"
request_idstringuuid
namestringSee Remote commands.
argsobjectPer command (M2b §7.2).
timeout_msinteger30000 in the deck's example.
{
  "type": "cmd",
  "request_id": "<uuid>",
  "name": "find_device",
  "args": {},
  "timeout_ms": 30000
}

error

server → boxjson≤ 1,024 BM2a §16

A message that could not be processed. Either side can send one.

This serverSent with no fields for bad JSON or an unknown type.

FieldTypeNotes
typestring"error"
Other fields: TBD
{
  "type": "error"
}

07Close and HTTP codes

CloseMeaning
1000normal (e.g. ACK-stall reconnect)
1001server going away
1002protocol error
1008unknown binary message, device_id mismatch (not enforced until layouts are known)
1009message too big
4000device revoked
4001replaced by a newer connection
4002unsupported protocol version
HTTPWhenBox
400malformed upgradeback-off
403certificate revoked or unknown1 attempt / 10 min
404wrong pathback-off
426WebSocket versionback-off
429too many connectswaits Retry-After
503maintenance, overloadwaits Retry-After

08Error codes

CodeMeaning
E_CONFIG_REV_CONFLICTset_config with a stale expected_rev. Returns the current values.
OthersTBD

09Remote commands

CommandRemote
find_deviceyes
get_configyes
get_logyes
ota_startyes
rebootyes
retry_uploadyes
run_network_diagnosticsyes
self_testyes
set_configyes
factory_resetno, button at the box
format_journalno, button at the box
remove_accountno, button at the box

10HTTP API

Needs a bearer token. Schema: Swagger, openapi.json. No token needed for /api/v1/stats and /health.

GET/api/v1/devicesBoxes seen since the last deploy
curl -s https://device-staging.airplx.com/api/v1/devices \
  -H "Authorization: Bearer $DEVICE_GW_ADMIN_TOKEN"
GET/api/v1/devices/123456One box, with its hello and last status
curl -s https://device-staging.airplx.com/api/v1/devices/123456 \
  -H "Authorization: Bearer $DEVICE_GW_ADMIN_TOKEN"
GET/api/v1/devices/123456/messages?limit=50Recent messages, both directions
curl -s https://device-staging.airplx.com/api/v1/devices/123456/messages?limit=50 \
  -H "Authorization: Bearer $DEVICE_GW_ADMIN_TOKEN"
POST/api/v1/devices/123456/cmdSend a command
curl -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"}'
GET/api/v1/devices/123456/commandsCommands and their results
curl -s https://device-staging.airplx.com/api/v1/devices/123456/commands \
  -H "Authorization: Bearer $DEVICE_GW_ADMIN_TOKEN"