AI-B100 HTTP API User Guide

Firmware version 1.9.7  |  See also: MQTT API User Guide

The AI-B100 also supports MQTT for event-driven integration. If your application connects to an MQTT broker rather than hosting a callback server, see MQTT API User Guide instead.


Introduction

The AI-B100 HTTP API lets your application control the gateway over a standard Ethernet LAN connection. You can join LoRaWAN networks, send uplinks, receive downlinks, change settings, and monitor gateway health — all using plain HTTP requests from any language or platform.

The API follows an asynchronous model: most operational commands (join, send, status) return 202 Accepted immediately, and the actual result arrives later as an HTTP POST from the gateway to a URL on your server. This is because LoRaWAN operations take measurable time — typically several seconds. Your application must expose a small HTTP server to receive these callbacks.

If you prefer a publish-subscribe model without hosting a callback server, consider using the MQTT interface instead.


Before You Start

Before using the HTTP API you need the following in place:

  1. Gateway IP address. The gateway must be reachable from your application over Ethernet. If you do not know its IP check your DHCP server's lease table. You can also browse to the gateway's web interface — the IP is shown on the LAN Settings page.

  2. A server your application controls. For operational commands (join, send, status, linkcheck) the gateway POSTs results back to a URL you configure. This server must be reachable from the gateway on the same LAN or a routed network. The URL is called the callback.

  3. HTTP API enabled. The API is enabled separately from the web interface. Check with:

    curl http://<gateway-ip>/get?http_api_enable

    If it returns {"http_api_enable": 0}, enable it:

    curl -X POST http://<gateway-ip>/set \
      -H "Content-Type: application/json" \
      -d '{"http_api_enable": 1}'

    No restart is required for this change.

  4. One request at a time. The gateway uses a single network socket. Do not send a second API request until the previous one has completed. This applies to web browser sessions as well — do not leave a settings page open in a browser while your application is making API calls.


Quick Start

The steps below take you from a freshly configured gateway to receiving your first LoRaWAN uplink result. Each step links to the relevant section for details.

Step 1 — Verify the gateway is alive

curl http://<gateway-ip>/info

You should receive a JSON object with firmware version, IP address, and device EUI. If this fails, check network connectivity and the gateway IP. See Query Device Information.

Step 2 — Configure your callback endpoints

Replace 192.168.1.50 and /lorawan/status / /lorawan/rx with your server's IP, port, and paths:

curl -X POST http://<gateway-ip>/set \
  -H "Content-Type: application/json" \
  -d '{
    "http_api_enable": 1,
    "callback_addr": "192.168.1.50",
    "callback_port": 8080,
    "callback_status_uri": "/lorawan/status",
    "callback_receive_uri": "/lorawan/rx"
  }'

Step 3 — Verify LoRaWAN credentials are set

curl http://<gateway-ip>/get?dev_eui
curl http://<gateway-ip>/get?join_eui

If they are wrong or missing, set them now. See LoRaWAN Credentials.

Step 4 — Join the network

curl http://<gateway-ip>/join

The gateway returns 202 Accepted and begins the join procedure. Watch your callback server — you will receive a POST with "status": 7 when the join succeeds.

Step 5 — Send your first uplink

curl -X POST http://<gateway-ip>/send \
  -H "Content-Type: application/json" \
  -d '{"port": 1, "confirm": 0, "payload": "Hello"}'

Step 6 — Read the result on your callback server

Your callback server receives a POST to /lorawan/status with the send result. If a downlink was returned by the network, your server also receives a POST to /lorawan/rx. See Receive a Downlink.


Core Concepts

The Callback Model

LoRaWAN operations take time: a join attempt waits for a network server response, and an uplink waits for an RX1/RX2 window before any downlink is received. Because of this, the gateway does not hold the HTTP connection open while it works.

Instead:

  1. You send a request (GET /join, POST /send, etc.).
  2. The gateway responds immediately with 202 Accepted — this only means the command was queued, not that it succeeded.
  3. When the operation finishes, the gateway POSTs the result to your configured callback URL.
Your App AI-B100 LoRaWAN Network | | | |--- GET /join ->| | |<- 202 Accepted-| | | |-- Join Request ----->| | |<- Join Accept -------| |<--- POST /your-callback-url --------- (from gateway)

What this means for your application:

What happens if the callback server is unreachable?
The gateway attempts the callback once. If the connection fails, or the server returns an HTTP error (4xx/5xx), the gateway retries up to a threshold and then enters a 30-second backoff before trying again. The result is not lost — it is queued and will be delivered when connectivity is restored.

Gateway Name and the $name$ Tag

Each gateway has a configurable name (up to 16 characters, letters, digits, hyphens, and underscores). The default is AI-B100.

The name is included as the "name" field in every callback payload — this lets you identify which gateway sent a callback when you have multiple units posting to the same server endpoint.

You can also embed $name$ inside callback path strings. It is replaced with the configured gateway name when each callback is sent:

Setting storedPath used at runtime
/gateway/$name$/status/gateway/my-gw-01/status
/lorawan/$name$/rx/lorawan/my-gw-01/rx

This lets you route callbacks from multiple gateways to a single server without changing the path configuration on each unit — only the name needs to differ.

Data Rate and Duty Cycle

Data rate controls the balance between range and payload capacity:

data_rateSpreading factorMax payload (maxUp)
0SF12 (longest range)51 bytes
1SF1151 bytes
2SF1051 bytes
3SF9115 bytes
4SF8222 bytes
5SF7 (shortest range)222 bytes

The maxUp field in every status callback tells you the maximum payload size allowed at the current data rate. Always check maxUp before sending — if your payload is larger, the gateway will reject it with status: 3.

Duty cycle limits how often a device can transmit on a given frequency band. When a send is blocked by duty cycle, the callback returns status: 13 and includes next_upload_ms — the number of milliseconds you must wait before the next uplink will be accepted. A value of 0 means there is no active restriction.

Status Codes at a Glance

The status field in every callback payload carries a numeric code. Here is what each one means and what action (if any) to take:

CodeEventWhat to do
0Ready — gateway joined and idleNo action needed
1Gateway started, waiting for joinSend GET /join
2Send attempted but payload was emptyCheck your /send request body
3Payload too long for current data rateReduce payload or increase data rate
4Join attempt failedRetry with GET /join, or wait for automatic retry
5Gateway started, auto join in progressWait for status 7
6Unknown LoRaWAN errorCheck LoRaWAN credentials and coverage
7Joined successfullyReady to send
8Downlink received (see receive callback)Read receive callback payload
9Uplink sent, no downlinkNormal — unconfirmed send succeeded
10Uplink confirmed by networkNormal — confirmed send acknowledged
11Uplink not confirmed (no ACK)Retry if critical; check join_retry and coverage
13Duty cycle restriction — uplink blockedWait next_upload_ms before retrying
14LoRaWAN session lostSend GET /join to rejoin
15Invalid LoRaWAN port numberUse FPort 1–223
16Uplink failed (radio error)Retry; check hardware
17Parameter validation errorCheck parameter names and values in your request
18Command rejected — not joined yetJoin first, then retry
19Setting saved successfullyNo action needed

Configuring the Gateway

Reading Current Settings

GET /get returns all current parameters as a JSON object. You can also request a single parameter by name.

# Get everything
curl http://<gateway-ip>/get

# Get one parameter
curl http://<gateway-ip>/get?callback_addr

Response (single parameter example):

{"callback_addr": "192.168.1.50"}

Changing Settings

POST /set updates one or more parameters in a single request. All parameters are validated before any are saved — if one fails, none are written.

curl -X POST http://<gateway-ip>/set \
  -H "Content-Type: application/json" \
  -d '{"adr_enable": 1, "data_rate": 3}'

Successful response:

{"status": 19, "restart_required": 0}

If any parameter changes require a restart, restart_required is 1. Apply changes that need a restart by calling GET /restart after the /set call.

If a parameter is invalid:

{"status": 17, "failed": "data_rate"}

The failed field names the first parameter that did not validate. Fix it and try again.

Network Settings

By default the gateway uses DHCP. To assign a static IP:

curl -X POST http://<gateway-ip>/set \
  -H "Content-Type: application/json" \
  -d '{
    "dhcp_enable": 0,
    "ip_addr": "192.168.1.131",
    "gateway_addr": "192.168.1.1",
    "dns_addr": "192.168.1.1",
    "subnet_mask": "255.255.255.0"
  }'

This requires a restart. After restart, connect to the new IP. The old IP will no longer respond.

To return to DHCP:

curl -X POST http://<gateway-ip>/set \
  -H "Content-Type: application/json" \
  -d '{"dhcp_enable": 1}'

Followed by GET /restart.

Callback Settings

Callbacks are the mechanism the gateway uses to deliver results to your application. Five settings control where callbacks are sent:

ParameterDescription
callback_addrIPv4 address of your callback server
callback_portTCP port your callback server listens on
callback_status_uriPath for status callbacks (join results, send results)
callback_receive_uriPath for receive callbacks (downlinks, linkcheck results)
callback_gps_uriPath for GPS position callbacks (optional)

Example — configure callbacks and enable the API in one request:

curl -X POST http://<gateway-ip>/set \
  -H "Content-Type: application/json" \
  -d '{
    "http_api_enable": 1,
    "callback_addr": "192.168.1.50",
    "callback_port": 8080,
    "callback_status_uri": "/lorawan/status",
    "callback_receive_uri": "/lorawan/rx"
  }'

No restart is needed for callback settings.

Using the $name$ tag in callback paths:

If you run multiple gateways posting to the same server, embed $name$ in the path. The gateway replaces it with the configured gateway name on each callback:

curl -X POST http://<gateway-ip>/set \
  -H "Content-Type: application/json" \
  -d '{
    "callback_status_uri": "/lorawan/$name$/status",
    "callback_receive_uri": "/lorawan/$name$/rx"
  }'

With name set to gw-01, callbacks arrive at /lorawan/gw-01/status and /lorawan/gw-01/rx.

LoRaWAN Credentials

LoRaWAN OTAA credentials must match what is registered on the network server. They require a restart after being changed.

curl -X POST http://<gateway-ip>/set \
  -H "Content-Type: application/json" \
  -d '{
    "dev_eui": "9E139EFFFE98DC98",
    "join_eui": "0000000000000000",
    "app_key": "00112233445566778899AABBCCDDEEFF"
  }'

Then restart:

curl http://<gateway-ip>/restart
ParameterDescriptionLength
dev_euiDevice EUI — unique identifier for this device16 hex chars
join_euiJoin/Application EUI — identifies the application on the network server16 hex chars
app_keyApplication key — used to derive session keys during join32 hex chars
nwk_keyNetwork key — required for LoRaWAN 1.1 only32 hex chars

Gateway Name

Set a name to identify this gateway in callback payloads and callback paths. The name appears as the "name" field in every callback.

curl -X POST http://<gateway-ip>/set \
  -H "Content-Type: application/json" \
  -d '{"name": "warehouse-01"}'

Allowed characters: letters (AZ, az), digits (09), hyphen (-), underscore (_). Maximum 16 characters. No restart is required.

Restarting the Gateway

Required after any parameter marked with an X in the restart column of the All Parameters table.

curl http://<gateway-ip>/restart

The gateway responds with 202 Accepted and restarts within two seconds. Poll GET /info to confirm it is back:

# Wait a few seconds, then:
curl http://<gateway-ip>/info

Operations

Check Gateway Status

Requests the current gateway status. The result arrives on your callback_status_uri.

curl http://<gateway-ip>/status

Returns 202 Accepted. Your callback server receives a status payload shortly after. A typical joined-and-ready response:

{
    "name": "warehouse-01",
    "status": 0,
    "dev_addr": "26011BDA",
    "confirmed": 1,
    "fcntUp": 592,
    "data_rate": 3,
    "maxUp": 115,
    "tUnix": 1775036333,
    "next_upload_ms": 0
}

status: 0 with a non-zero dev_addr confirms the gateway is joined and ready to send.

Requires: status callback configured. Returns 412 Precondition Failed if not.

Join the LoRaWAN Network

Initiates an OTAA join. If already joined, the gateway rejoins.

Simple join:

curl http://<gateway-ip>/join

Join with parameters:

curl "http://<gateway-ip>/join?data_rate_join=0&adr_enable=1&data_rate=3&join_retry=5"

Or via POST:

curl -X POST http://<gateway-ip>/join \
  -H "Content-Type: application/json" \
  -d '{
    "data_rate_join": 0,
    "adr_enable": 1,
    "data_rate": 3,
    "join_retry": 5
  }'

Join parameters:

ParameterDescriptionDefault
data_rate_joinData rate for join requests (0=SF12, 5=SF7)0
adr_enableEnable Adaptive Data Rate1
data_rateData rate to use after joining3
join_retryMax join attempts per round before stopping5

Setting any of these via /join also saves them for future sessions.

Retry behavior:

The gateway retries automatically up to join_retry times before stopping. Each failed attempt delivers status: 4 to your callback. Backoff between attempts:

Attempt numberDelay before next attempt
1–315 seconds
4–1060 seconds
11 and above120 seconds

Once the retry limit is reached, the gateway waits for a manual GET /join before starting a new round.

Requires: status callback configured. Returns 412 Precondition Failed if not.

Queues a LoRaWAN uplink. The send result arrives on your callback_status_uri. If the network sends a downlink, it arrives on your callback_receive_uri.

curl -X POST http://<gateway-ip>/send \
  -H "Content-Type: application/json" \
  -d '{"port": 1, "confirm": 0, "payload": "Hello"}'

Fields:

FieldRequiredDescription
portYesLoRaWAN FPort, 1–223
payloadYesData to send — see payload formats below
confirmNo1 = confirmed uplink (requests ACK), 0 = unconfirmed. Default: 0
encodingNoSet to "base64" to send a Base64-encoded payload string. The gateway decodes it to raw bytes before transmission. Omit for formats A, B, and C.
lengthNoOverride byte count. Must match actual payload length if provided. When encoding is "base64", this is the decoded byte count, not the Base64 string length.

Payload formats:

Format A — ASCII string. Use when your data is text. The string is sent byte-for-byte.

{"port": 1, "confirm": 0, "payload": "Hello"}

Format B — Byte array. Use when your data is binary. Each element must be 0–255.

{"port": 1, "confirm": 1, "payload": [0x01, 0x02, 0xFF]}

Format C — Node.js Buffer object. JSON.stringify() on a Node.js Buffer produces this envelope. The gateway unwraps it automatically — use this format as-is from Node-RED or JavaScript.

{"port": 1, "confirm": 0, "payload": {"type": "Buffer", "data": [0, 1, 2, 3]}}

Format D — Base64 string. Use when your data originates as Base64. Set "encoding": "base64" and put the Base64-encoded bytes in payload. The gateway decodes them to raw bytes before transmitting over LoRaWAN.

{"port": 1, "confirm": 0, "encoding": "base64", "payload": "AQID/w=="}

"AQID/w==" decodes to the four bytes [0x01, 0x02, 0x03, 0xFF]. If you supply length, it must equal the decoded byte count (here: 4), not the Base64 string length.

Send result status codes:

statusMeaning
9Uplink sent, no downlink received
10Uplink confirmed — ACK received from network
11Uplink not confirmed — no ACK (try again if critical)
13Blocked by duty cycle — check next_upload_ms and retry later
16Uplink failed — radio error
Requires: Both status and receive callbacks configured. Returns 412 Precondition Failed if not. Returns 409 Conflict if not joined.

Checking maxUp before sending: The maxUp field in the last status callback tells you how many bytes you can send at the current data rate. Sending more returns status: 3 (payload too long) and the transmission is not attempted.

Downlinks arrive unsolicited on your callback_receive_uri whenever the LoRaWAN network sends a message back to the gateway, or in response to a linkcheck.

A typical downlink with payload:

POST /lorawan/rx HTTP/1.1
Host: 192.168.1.50:8080
Content-Type: application/json

{
    "name": "warehouse-01",
    "confirmed": 1,
    "fcntDown": 38,
    "rssi": -70,
    "snr": 10.0,
    "tUnix": 1775036333,
    "next_upload_ms": 0,
    "port": 5,
    "length": 4,
    "payload": [0x01, 0x02, 0x03, 0x04]
}

If the payload is printable ASCII, payload is returned as a string. If any byte is non-printable, it is returned as an array of integers.

A linkcheck response (no payload):

POST /lorawan/rx HTTP/1.1
Host: 192.168.1.50:8080
Content-Type: application/json

{
    "name": "warehouse-01",
    "confirmed": 1,
    "fcntDown": 5,
    "rssi": -70,
    "snr": 10.0,
    "tUnix": 1775036333,
    "margin": 22,
    "gwCount": 1,
    "next_upload_ms": 0,
    "length": 0
}

margin and gwCount are only present in linkcheck responses.

Key fields:

FieldDescription
confirmed1 if the previous uplink was acknowledged by the network
fcntDownDownlink frame counter — use to detect missed messages
rssiSignal strength in dBm
snrSignal-to-noise ratio in dB
tUnixUTC Unix timestamp (synchronized via network time during linkcheck, 0 if not yet synchronized)
lengthByte count of payload (0 = no downlink data, linkcheck only)
marginLink margin from linkcheck (larger is better)
gwCountNumber of network gateways that heard the uplink

A link check sends a LinkCheckReq to the LoRaWAN network and receives back signal quality information — useful for verifying coverage without sending application data.

curl http://<gateway-ip>/linkcheck

The result arrives on your callback_receive_uri with margin and gwCount fields and length: 0.

Requires: receive callback configured. Returns 409 Conflict if not joined.

Query Device Information

GET /info returns a snapshot of gateway state and is always available regardless of whether the HTTP API is enabled.

curl http://<gateway-ip>/info

Example response:

{
    "HW": "AI-B100",
    "HW_ver": "1.3",
    "FW_ver": "1.9.7",
    "name": "warehouse-01",
    "power": "poe",
    "dhcp_enable": 0,
    "ip_addr": "192.168.1.131",
    "dev_eui": "9E139EFFFE98DC98",
    "dev_addr": "26011BDA",
    "restart_counter": 12,
    "mqtt_enable": 0,
    "http_api_enable": 1,
    "callback": "active",
    "tUnix": 1775036333,
    "TempC": 29.75
}
FieldDescription
dev_addr"0" = not joined; 8 hex chars = joined device address
callback"active" = server reachable; "fail" = server unreachable; "disabled" = no callback paths set
tUnix0 = network time not yet synchronized
TempCBoard temperature — only present on hardware revision 1.3
restart_counterCounts every hardware restart; useful for detecting unexpected reboots

Lifecycle Scenarios

These scenarios cover the typical application lifecycle. Each assumes callbacks are already configured. See Callback Settings if they are not.


Scenario 1: Manual Join

The gateway waits for an explicit join command. Use this when your application controls when joining happens.

Prerequisite: autojoin_enable = 0

sequenceDiagram participant App as Application participant GW as AI-B100 participant LNS as LoRaWAN Network GW->>App: Callback POST — status:1 (ready to join) App->>GW: GET /join GW-->>App: 202 Accepted loop Up to join_retry attempts (default: 5), backoff between each GW->>LNS: OTAA Join Request LNS-->>GW: Join Accept end GW->>App: Callback POST — status:7 (joined) Note over App,LNS: Ready to send — see Scenario 4

Step 1 — Gateway powers up.
Your callback server receives:

{
    "name": "warehouse-01",
    "status": 1,
    "dev_addr": "0",
    "confirmed": 0,
    "fcntUp": 0,
    "data_rate": 3,
    "maxUp": 115,
    "tUnix": 0,
    "next_upload_ms": 0
}

status: 1 means the gateway is ready but waiting for your join command. dev_addr: "0" confirms it is not yet joined.

Step 2 — Your application triggers a join:

curl http://<gateway-ip>/join

Returns 202 Accepted. The gateway retries up to join_retry times automatically. No action is needed during retries.

Step 3 — Join succeeds.
Your callback server receives:

{
    "name": "warehouse-01",
    "status": 7,
    "dev_addr": "26011BDA",
    "confirmed": 0,
    "fcntUp": 0,
    "data_rate": 3,
    "maxUp": 115,
    "tUnix": 0,
    "next_upload_ms": 0
}

status: 7 with a non-zero dev_addr confirms the join succeeded. Continue to Scenario 4.


Scenario 2: Auto Join

The gateway joins automatically after power-up. No join command is needed.

Prerequisite: autojoin_enable = 1

sequenceDiagram participant App as Application participant GW as AI-B100 participant LNS as LoRaWAN Network GW->>App: Callback POST — status:5 (auto join active) loop Up to join_retry attempts (default: 5), backoff between each GW->>LNS: OTAA Join Request (automatic) LNS-->>GW: Join Accept end GW->>App: Callback POST — status:7 (joined) Note over App,LNS: Ready to send — see Scenario 4

Step 1 — Gateway powers up and begins joining automatically.
Your callback server receives status: 5. No action required.

Step 2 — Join succeeds.
Your callback server receives status: 7. Continue to Scenario 4.

If all join_retry attempts fail, the gateway stops and waits for a manual GET /join. See Scenario 3.


Scenario 3: Join Retry Limit Reached

Applies when all attempts in a join round fail — either on auto join or after a manual GET /join.

sequenceDiagram participant App as Application participant GW as AI-B100 participant LNS as LoRaWAN Network Note over App,GW: Trigger: auto join (status:5) or GET /join loop join_retry attempts (default: 5) GW->>LNS: OTAA Join Request LNS--xGW: No response / rejected GW->>App: Callback POST — status:4 (join failed) end Note over GW: Waiting for manual join command App->>GW: GET /join GW-->>App: 202 Accepted loop Up to join_retry attempts GW->>LNS: OTAA Join Request LNS-->>GW: Join Accept end GW->>App: Callback POST — status:7 (joined)

Each failed attempt delivers:

{
    "name": "warehouse-01",
    "status": 4,
    "dev_addr": "0",
    ...
}

After join_retry consecutive failures the gateway stops. When you are ready to retry:

# Simple retry with current settings
curl http://<gateway-ip>/join

# Retry with updated retry count
curl "http://<gateway-ip>/join?join_retry=10"

Scenario 4: Normal Operation

Use this flow once the gateway is joined.

sequenceDiagram participant App as Application participant GW as AI-B100 participant LNS as LoRaWAN Network App->>GW: POST /send {port, confirm, payload} GW-->>App: 202 Accepted GW->>LNS: Uplink (confirmed or unconfirmed) LNS-->>GW: ACK + optional downlink GW->>App: Callback POST — status:10 (confirmed) GW->>App: Callback POST — receive (if downlink present) Note over GW,LNS: Session lost GW->>App: Callback POST — status:14 (connection lost) App->>GW: GET /join

Sending an uplink:

curl -X POST http://<gateway-ip>/send \
  -H "Content-Type: application/json" \
  -d '{"port": 1, "confirm": 1, "payload": "Hello"}'

Your callback server receives the result:

statusMeaning
9Sent, no downlink
10Sent and confirmed
13Blocked by duty cycle — wait next_upload_ms ms

If the LoRaWAN session is lost:
Your callback server receives status: 14. Send GET /join to start a new join round, or wait for the gateway to rejoin automatically if autojoin_enable = 1.


GPS

GPS functionality requires a GPS module to be connected. If no module is present the /gps endpoint still returns a response but with all fields zeroed and gps_status: 0.

Query Current Position

curl http://<gateway-ip>/gps

Example response with a 3D fix:

{
    "name": "warehouse-01",
    "ns": "N",
    "lat": 55.6761,
    "ew": "E",
    "lon": 12.5683,
    "alt": 12.5,
    "nosv": 8,
    "pdop": 1.8,
    "hdop": 1.2,
    "vdop": 1.4,
    "utc": "103045.00",
    "date": "090526",
    "sog": 0.0,
    "cog": 0.0,
    "gps_status": 3
}

gps_status values:

ValueMeaning
0No GPS module detected
1Module present but no fix yet
22D fix (latitude/longitude valid, altitude not reliable)
33D fix (latitude, longitude, and altitude all valid)

Coordinate format:
lat and lon are decimal degrees. ns and ew give the hemisphere. A position of lat: 55.6761, ns: "N", lon: 12.5683, ew: "E" is 55.6761°N, 12.5683°E.

utc is a string in HHMMSS.SS format — 103045.00 means 10:30:45.00 UTC.
date is DDMMYY090526 means 9 May 2026.

Periodic GPS Callbacks

The gateway can POST GPS position to your server at a regular interval without being polled.

curl -X POST http://<gateway-ip>/set \
  -H "Content-Type: application/json" \
  -d '{
    "callback_gps_uri": "/lorawan/gps",
    "gps_update_interval": 60
  }'

gps_update_interval is in seconds. Set to 0 to disable. No restart required.

The GPS callback payload is identical to the /gps response — a POST to your callback_gps_uri with the same JSON fields. The $name$ tag is supported in callback_gps_uri.


Callback Authentication

If your callback server requires authentication, the gateway supports HTTP Digest Authentication (RFC 7616, MD5). This ensures the password is never sent in plain text.

How it works:

  1. The gateway sends the callback POST without any Authorization header.
  2. If your server responds 401 Unauthorized with a WWW-Authenticate: Digest ... challenge, and credentials are configured on the gateway, the gateway re-sends the same POST with a computed Authorization: Digest ... header.
  3. If the credentials are accepted, the callback is processed normally.

Configure credentials:

curl -X POST http://<gateway-ip>/set \
  -H "Content-Type: application/json" \
  -d '{
    "callback_digest_user": "admin",
    "callback_digest_password": "s3cr3t"
  }'

To disable authentication:

curl -X POST http://<gateway-ip>/set \
  -H "Content-Type: application/json" \
  -d '{
    "callback_digest_user": "",
    "callback_digest_password": ""
  }'

What your server must send on the challenge:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Digest realm="AI-B100", nonce="abc123xyz", algorithm=MD5, qop="auth"
Content-Length: 0

The gateway supports algorithm=MD5 and qop=auth. A challenge without qop is also accepted. Other algorithms (SHA-256 etc.) are not supported.

What the gateway sends on the authenticated retry:

POST /lorawan/gw-01/status HTTP/1.1
Authorization: Digest username="admin", realm="AI-B100", nonce="abc123xyz",
    uri="/lorawan/gw-01/status", algorithm=MD5, qop=auth,
    nc=00000001, cnonce="a4f3c1e2",
    response="7b3f1c9e2a4d8f6e1b2c3d4e5f6a7b8c"

Reference

Endpoint Quick Reference

EndpointMethodAPI must be enabledReturns callback
/infoGETNoNo — response is immediate
/getGETNoNo — response is immediate
/setPOSTNoNo — response is immediate
/restartGET or POSTNoNo
/gpsGETNoNo — response is immediate
/statusGETYesStatus callback
/joinGET or POSTYesStatus callback
/linkcheckGETYesReceive callback
/sendPOSTYesStatus callback + receive callback (if downlink)

All Parameters

LoRaWAN parameters — all require a restart unless noted

ParameterTypeRangeRestart
lora_versioninteger0 = LoRaWAN 1.04 A, 1 = 1.10 A, 2 = 1.04 C, 3 = 1.10 CX
join_euihex string16 hex charsX
dev_euihex string16 hex charsX
app_keyhex string32 hex charsX
nwk_keyhex string32 hex chars (LoRaWAN 1.1 only)X
autojoin_enableinteger0 or 1
join_retryinteger1–99
data_rate_joininteger0–5 (0 = SF12, 5 = SF7)
data_rateinteger0–5 (0 = SF12, 5 = SF7)
adr_enableinteger0 or 1
duty_check_enableinteger0 or 1
lora_link_check_intervalinteger0–128 (uplinks between link checks, 0 = disabled)
lora_hb_intervalinteger0–65535 seconds (0 = disabled)
lora_hb_portinteger1–223

Network / LAN parameters

ParameterTypeRangeRestart
dhcp_enableinteger0 or 1X
ip_addrIPv4valid IPv4 addressX
gateway_addrIPv4valid IPv4 addressX
dns_addrIPv4valid IPv4 addressX
subnet_maskIPv4valid IPv4 addressX

HTTP API and callback parameters — no restart required

ParameterTypeRange
http_api_enableinteger0 or 1
callback_addrIPv4valid IPv4 address
callback_portinteger1–65535
callback_status_uristringmax 32 chars, supports $name$
callback_receive_uristringmax 32 chars, supports $name$
callback_gps_uristringmax 32 chars, supports $name$
gps_update_intervalinteger0–3600 seconds (0 = disabled)
callback_digest_userstringmax 32 chars (empty = disabled)
callback_digest_passwordstringmax 48 chars (empty = disabled)

MQTT parameters — restart required unless noted

ParameterTypeRangeRestart
mqtt_enableinteger0 or 1X
broker_addrIPv4valid IPv4 addressX
broker_portinteger1–65535X
mqtt_userstringmax 16 charsX
mqtt_passwordstringmax 16 charsX
send_topicstringmax 31 chars, supports $name$X
receive_topicstringmax 31 chars, supports $name$X
status_topicstringmax 31 chars, supports $name$X
setup_topicstringmax 31 chars, supports $name$X
gps_topicstringmax 31 chars, supports $name$X
mqtt_hb_intervalinteger0–65535 seconds (0 = disabled)
mqtt_gps_intervalinteger0–3600 seconds (0 = disabled)

System parameters

ParameterTypeRangeRestart
namestringmax 16 chars: A–Z, a–z, 0–9, -, _
watchdog_minutesinteger0–65535 minutes (0 = disabled)
ota_modeinteger0 = off, 1 = once, 2 = auto, 3 = force
ota_ssidstringmax 31 chars
ota_passwordstringmax 31 chars

HTTP Status Codes

Codes returned in the HTTP response to your requests (not to be confused with the LoRaWAN status codes in callback payloads):

CodeMeaning in AI-B100 context
200 OKSettings read or written successfully
202 AcceptedOperational command queued — result will arrive via callback
400 Bad RequestInvalid parameter name, value out of range, or malformed JSON
404 Not FoundUnknown endpoint path
405 Method Not AllowedWrong HTTP method (e.g. POST to a GET-only endpoint)
409 ConflictCommand rejected because the gateway is not joined
411 Length RequiredPOST request missing Content-Length header
412 Precondition FailedRequired callback not configured
413 Content Too LargePayload exceeds current maxUp limit, or JSON body > 1024 bytes
500 Internal Server ErrorJoin already in progress, or FRAM settings write failed
503 Service UnavailableHTTP API disabled, or command queue full — retry shortly

Callback Payload Fields

Status callback fields (delivered to callback_status_uri)

FieldTypeDescription
namestringGateway name
statusintegerStatus code — see Status Codes at a Glance
dev_addrstring8-char hex device address, or "0" if not joined
confirmedinteger1 if the last uplink was acknowledged by the network
fcntUpintegerUplink frame counter
data_rateintegerCurrent uplink data rate (0 = SF12, 5 = SF7)
maxUpintegerMax payload bytes at current data rate
tUnixintegerUTC Unix timestamp in seconds, or 0 if not synchronized
next_upload_msintegerMilliseconds until next uplink allowed (0 = no restriction)

Receive callback fields (delivered to callback_receive_uri)

FieldTypeDescription
namestringGateway name
confirmedinteger1 if the last uplink was acknowledged
fcntDownintegerDownlink frame counter
rssifloatSignal strength in dBm
snrfloatSignal-to-noise ratio in dB
tUnixintegerUTC Unix timestamp in seconds, or 0
marginintegerLink margin from linkcheck (linkcheck responses only)
gwCountintegerNumber of gateways that heard the uplink (linkcheck only)
next_upload_msintegerMilliseconds until next uplink allowed
lengthintegerPayload byte count (0 = no downlink data)
portintegerLoRaWAN FPort (only present if length > 0)
payloadstring or arrayDownlink bytes — string if all printable ASCII, integer array otherwise (only present if length > 0)

GPS callback fields (delivered to callback_gps_uri)

FieldTypeDescription
namestringGateway name
nsstring"N" or "S"
latfloatLatitude in decimal degrees
ewstring"E" or "W"
lonfloatLongitude in decimal degrees
altfloatAltitude in metres above mean sea level
nosvintegerNumber of satellites in use
pdopfloatPosition dilution of precision
hdopfloatHorizontal dilution of precision
vdopfloatVertical dilution of precision
utcstringUTC time HHMMSS.SS
datestringDate DDMMYY
sogfloatSpeed over ground in knots
cogfloatCourse over ground in degrees true

Troubleshooting

412 Precondition Failed on /join, /status, or /send

The required callback is not configured. Check that callback_addr, callback_port, and the relevant URI (callback_status_uri for join/status, callback_receive_uri for send/linkcheck) are all non-empty:

curl http://<gateway-ip>/get?callback_addr
curl http://<gateway-ip>/get?callback_status_uri
curl http://<gateway-ip>/get?callback_receive_uri

Also check that http_api_enable is 1.


409 Conflict on /send or /linkcheck

The gateway is not joined. Call GET /join first and wait for a status: 7 callback before sending.


Gateway returns 202 Accepted but no callback arrives

The gateway cannot reach your callback server. Check:

  1. callback_addr is set to your server's IP (not 0.0.0.0 or the gateway's own IP).
  2. Your server is listening on callback_port.
  3. No firewall is blocking incoming connections on that port from the gateway's IP.
  4. Check GET /info — the callback field shows "active" if the TCP connection succeeds, "fail" if it cannot connect.
curl http://<gateway-ip>/info | grep callback

Join keeps failing (status: 4 repeatedly)

  1. Credentials — verify dev_eui, join_eui, and app_key match the network server registration exactly.
  2. Coverage — if the device is indoors or far from a gateway, try data_rate_join=0 (SF12, longest range).
  3. Duty cycle — after several join attempts, the radio enters a mandatory quiet period. Wait a minute and try again.
  4. Network server — confirm the device is registered and activated on the network server.

Duty cycle blocking sends (status: 13)

The next_upload_ms field in the callback tells you exactly how long to wait. Do not retry before that time elapses.

{"status": 13, "next_upload_ms": 45000, ...}

Wait 45 seconds, then retry. If this happens frequently, consider increasing the uplink interval or switching to a higher data rate (shorter transmit time = less duty cycle consumption).


Payload rejected as too long (status: 3)

Check maxUp in the last status callback — this is the limit at the current data rate. Either shorten the payload or increase data_rate to a higher number (e.g. data_rate: 4 or 5 allows up to 222 bytes). Note that higher data rates have shorter range.


GPS returns all zeros or gps_status: 0

No GPS module is connected, or the module has not yet acquired a fix. gps_status: 1 means the module is present but still searching — this is normal for the first minute or two after power-up, especially indoors. Wait for gps_status: 3 (3D fix) before trusting the coordinates.