Heller Enterprise Networks
Features Is It For Me? Specs Setup Guide API Docs BETA Contact About

API REFERENCE Beta

Every feature in the NOTBOX web UI is powered by a local HTTP API. Call these endpoints directly to automate testing, build scripts, or integrate with CI/CD pipelines.

Beta API: This API is functional but still under active development. Endpoints, parameters, and response formats may change between firmware versions. Please report any issues to [email protected].

Quick Start

All endpoints are available at http://<device-ip>/banana/<endpoint>

Replace <device-ip> with your NOTBOX management IP (shown in the web UI).

# Check device Serial
curl "http://192.168.1.100/banana/get_serial"

# Add 100ms latency + 2% packet loss to downloads
curl -X POST "http://192.168.1.100/banana/set_impairment?interface=download&delay=100&jitter=20&loss=2"

# Check current impairment status
curl "http://192.168.1.100/banana/get_status"

# Clear all impairments
curl -X POST "http://192.168.1.100/banana/clear_impairment?interface=both"

# Start a 60-second packet capture
curl -X POST "http://192.168.1.100/banana/start_capture?duration=60"

# Run a speed test
curl -X POST "http://192.168.1.100/banana/run_speedtest"

All responses are JSON. No authentication is required by default. When password protection is enabled, pass an API token:

# With Bearer token (generate one in Settings > API Access Token)
curl -H "Authorization: Bearer <example-token>" "http://192.168.1.100/banana/get_status"

API Conventions

Every endpoint shares the same rules for requests, responses, and errors. Read this once and you're set for the rest of the docs.

HTTP Methods

Endpoints that change something on the device accept POST only. Endpoints that just return data accept either GET or POST. Each endpoint below is tagged with its method.

Authentication

The API is open by default. No login or token needed. When you turn on password protection in Settings, authenticate with either:

See the Authentication section below for the full flow.

Browser Restriction

Your browser can only call the API when the web page is served from NOTBOX itself. If you build a custom dashboard and run it at http://localhost:3000, the browser will get 403 Forbidden when it tries to POST to NOTBOX. This protects your device from a sketchy website secretly firing commands at it.

Tools like curl, Python scripts, and Postman aren't affected. They don't have this restriction.

Rate Limit

Up to 30 requests per second per IP address. Going over returns 429 Too Many Requests. Normal use never hits this. It's there to catch runaway scripts.

Request Size

Requests larger than 64 KiB are rejected with 413 Payload Too Large. All normal API requests are far smaller.

Response Format

Endpoints return JSON. A successful call looks like one of these:

# Action endpoint
{
  "status": "success",
  "message": "Impairment applied to download"
}

# Data endpoint (shape varies)
{
  "serial": "NB20260412065"
}

Errors look like:

{
  "status": "error",
  "message": "Invalid delay (must be 0-10000 ms)"
}

Check the status field first. A few low-level errors (rate limit, wrong method, etc.) come back as plain text instead of JSON. In those cases the HTTP status code tells you what happened.

HTTP Status Codes

CodeMeaningWhat to do
200OKCheck the JSON status field for the actual outcome
400Bad RequestA parameter is missing or wrong; check the message
401UnauthorizedAdd your API token or log in
403ForbiddenBrowser is calling from a page not served by NOTBOX
404Not FoundThe endpoint, file, or resource doesn't exist
405Method Not AllowedYou used GET on a POST-only endpoint
413Payload Too LargeYour request body is over 64 KiB
429Too Many RequestsSlow down. You're over the rate limit
500Internal Server ErrorUnusual. Try again; contact support if it persists

Endpoints

Impairment Control

POST /banana/set_impairment Apply network impairments to an interface

Parameters

NameRequiredDescription
interfacerequiredupload, download, or both
delayoptionalLatency in milliseconds (default: 0)
jitteroptionalJitter in milliseconds (default: 0)
lossoptionalPacket loss percentage (default: 0)
bandwidthoptionalBandwidth limit value (default: 0 = unlimited)
bandwidth_unitoptionalmbit (default), kbit, or gbit

Example

curl -X POST "http://192.168.1.100/banana/set_impairment?interface=download&delay=100&jitter=20&loss=2&bandwidth=10&bandwidth_unit=mbit"
{
  "status": "success",
  "message": "Impairment applied to download"
}
download applies to wired + both WiFi bands automatically. both applies to all directions in a single call.
POST /banana/clear_impairment Remove all impairments from an interface

Parameters

NameRequiredDescription
interfacerequiredupload, download, or both

Example

curl -X POST "http://192.168.1.100/banana/clear_impairment?interface=both"
{
  "status": "success",
  "message": "Impairment cleared on both"
}
GET /banana/get_status Get current impairment status on all interfaces

Parameters

None

Response

{
  "active": true,
  "download": {
    "latency": "100",
    "jitter": "20",
    "loss": "2",
    "bandwidth": "10"
  },
  "upload": {
    "latency": "",
    "jitter": "",
    "loss": "",
    "bandwidth": ""
  }
}
active is true if any interface has impairments. The download and upload objects contain the parsed values. Empty strings mean no impairment set.

Packet Capture

POST /banana/start_capture Start a packet capture with optional filters

Parameters

NameRequiredDescription
durationoptionalCapture duration in seconds (default: 60)
ip_filteroptionalIP filter as direction:value. Direction: src, dst, either. Value: single IP, CIDR, or range (e.g. dst:192.168.1.0/24)
port_filteroptionalPort filter as direction:port (e.g. either:443)
mac_filteroptionalMAC filter as direction:MAC (e.g. src:aa:bb:cc:dd:ee:ff)
proto_filteroptionalProtocol filter as direction:protocol. Built-in: dns, dhcp, http, https, ssh, ntp, mdns, ssdp, mqtt, snmp
use_networkoptionalSet to true to save capture to mounted SMB share

Example

# Capture HTTPS traffic for 120 seconds
curl -X POST "http://192.168.1.100/banana/start_capture?duration=120&proto_filter=either:https"
{
  "status": "success",
  "filename": "notbox_20260216_143022.pcap",
  "pid": 12345,
  "duration": 120,
  "network_storage": ""
}
Local captures are capped at 50MB. Network storage captures have no size limit. Only one capture can run at a time.
POST /banana/stop_capture Stop a running capture

Parameters

None

Response

{
  "status": "success",
  "filename": "notbox_20260216_143022.pcap",
  "size": 5242880,
  "auto_stopped": false,
  "stop_reason": ""
}
GET /banana/get_capture_status Check capture progress

Parameters

None

Response

{
  "status": "capturing",
  "size": 1048576,
  "size_mb": 1,
  "elapsed": 15,
  "duration": 60
}
Returns "status": "stopped" when no capture is running.
GET /banana/download_capture Download a PCAP file

Parameters

NameRequiredDescription
filenamerequiredPCAP filename (e.g. notbox_20260216_143022.pcap)

Example

# Download to local machine
curl -o capture.pcap "http://192.168.1.100/banana/download_capture?filename=notbox_20260216_143022.pcap"
Returns binary PCAP data with Content-Type: application/vnd.tcpdump.pcap. Open with Wireshark.
POST /banana/delete_capture Delete capture files

Parameters

NameRequiredDescription
filenameoptionalSpecific PCAP filename to delete. If omitted, deletes all local captures.

Response

{
  "status": "success",
  "message": "Capture deleted",
  "location": "local"
}
POST /banana/get_live_stats Live and post-capture summary: hosts, conversations, DNS/SNI, protocols, TCP health

Parameters

NameRequiredDescription
filenamerequiredPCAP filename returned from start_capture

Example

curl -X POST "http://192.168.1.100/banana/get_live_stats?filename=notbox_20260526_001518.pcap"

Response

{
  "status": "success",
  "summary_version": 2,
  "live": true,
  "dns": [{ "name": "example.com", "count": 5, "ips": "93.184.216.34", "status": "NOERROR" }],
  "top_dest": [{ "ip": "93.184.216.34", "bytes": 2097152, "proto": "tcp", "port": "443", "name": "example.com" }],
  "ip_correlations": [{ "ip": "93.184.216.34", "dns_names": "example.com", "sni_names": "example.com" }],
  "top_conversations": [{ "endpoint_a": "192.168.1.10:55312", "endpoint_b": "93.184.216.34:443", "bytes": 1048576, "bytes_a_to_b": 524288, "bytes_b_to_a": 524288 }],
  "protocols": [{ "name": "tls", "packets": 420, "bytes": 4123456, "pct_bytes": 78.7 }],
  "tcp_health": { "out_of_order": 2, "dup_acks": 5, "resets": 1, "syn_retries": 0, "failed_handshakes": 1 },
  "failures": [{ "ip": "192.168.1.1", "count": 3, "type": "rst" }],
  "sni": [{ "name": "example.com", "count": 8, "ip": "93.184.216.34", "confidence": "high" }],
  "timeline": [{ "ts": 1715000000, "bytes": 102400, "packets": 42 }],
  "timeline_bucket_sec": 1,
  "total_bytes": 5242880,
  "upload_bytes": 2097152,
  "download_bytes": 3145728,
  "packet_count": 542,
  "duration": 14,
  "first_ts": 1715000000,
  "last_ts": 1715000014
}
Returns the current capture summary in sub-millisecond time. Safe to poll once per second while a capture is running. The live field is true during capture and false once it stops; the sort order switches from newest-first (during) to highest-traffic-first (after). If the filename has no data (for example after a device reboot, or you've called delete_capture), the response is {"status":"not_found","message":"No live capture state for that filename"}.
POST /banana/port_mirror Mirror all traffic to the LOCAL port for external capture

Parameters

NameRequiredDescription
actionrequiredstart, stop, or status

Example

# Enable port mirror (LOCAL port becomes mirror-only)
curl -X POST "http://192.168.1.100/banana/port_mirror?action=start"
{
  "success": true,
  "status": "active",
  "message": "Port mirror enabled - LOCAL port is now mirror-only"
}
When active, the LOCAL port loses its IP and becomes a mirror-only tap. Blue LED indicates mirror mode. Connect a device running Wireshark to capture all bridge traffic in real time.

Monitoring

POST /banana/start_uplink_monitor Start continuous latency/loss monitoring

Parameters

NameRequiredDescription
targetoptionalHost to ping (default: 8.8.8.8)
intervaloptionalPing interval in seconds, 1-300 (default: 5)
tab_idoptionalTab identifier for multiple concurrent monitors (default: default)

Response

{
  "status": "success",
  "pid": 1234,
  "target": "8.8.8.8",
  "interval": 5,
  "resolved_ip": "8.8.8.8",
  "tab_id": "default"
}
POST /banana/stop_uplink_monitor Stop a latency monitor

Parameters

NameRequiredDescription
tab_idoptionalTab identifier to stop (default: default)

Response

{ "status": "success", "tab_id": "default" }
GET /banana/get_uplink_data Retrieve latency/loss time-series data

Parameters

NameRequiredDescription
tab_idoptionalTab identifier (default: default)
timeframeoptionalTime window in seconds (default: 300). Ignored if start/end provided.
startoptionalCustom start time (Unix timestamp)
endoptionalCustom end time (Unix timestamp)

Response

{
  "status": "running",
  "target": "8.8.8.8",
  "tab_id": "default",
  "data": [
    { "timestamp": 1705000005, "latency": 25.5, "loss": 0.0 },
    { "timestamp": 1705000020, "latency": 26.3, "loss": 0.0 }
  ],
  "point_count": 2,
  "bucket_size": 5
}
Data is auto-aggregated into buckets (5s to 4h) targeting 150-200 data points. Latency in ms, loss as percentage.
GET /banana/list_monitors List all active uplink monitors

Parameters

None

Response

{
  "status": "success",
  "monitors": [
    { "tab_id": "default", "target": "8.8.8.8", "interval": 5, "pid": 1234, "data_points": 150 }
  ],
  "count": 1
}
POST /banana/bandwidth_monitor Start/stop/check bandwidth throughput monitor

Parameters

NameRequiredDescription
actionrequiredstart, stop, or status
interfaceoptionalauto (default), upload, download, wifi

Example

curl -X POST "http://192.168.1.100/banana/bandwidth_monitor?action=start&interface=auto"
{ "status": "success", "pid": 2345, "interface": "auto" }
auto monitors the downlink + active WiFi together, with direction correction so RX = download and TX = upload from the test device's perspective.
GET /banana/get_bandwidth_data Retrieve bandwidth time-series data

Parameters

NameRequiredDescription
timeframeoptionalTime window in seconds (default: 300)
startoptionalCustom start time (Unix timestamp)
endoptionalCustom end time (Unix timestamp)

Response

{
  "status": "success",
  "interface": "auto",
  "data": [
    { "timestamp": 1705000005, "rx_rate": 1024000, "tx_rate": 512000 }
  ],
  "point_count": 1,
  "bucket_size": 5,
  "totals": { "rx": 10485760, "tx": 5242880, "duration": 120 }
}
Rates in bytes/second. Totals in bytes. Auto-aggregated into buckets targeting 150-200 points.

Network Tools

POST /banana/network_tools Ping, DNS, traceroute, ARP scan, IP lookup, public IP

Parameters

NameRequiredDescription
toolrequiredTool to run: ping, dns, rdns, traceroute, arp, arpscan, iplookup, publicip, update_oui
targetrequired*Target host/IP. Required for: ping, dns, rdns, traceroute, iplookup. Not needed for: arp, arpscan, publicip, update_oui.
countoptionalPing count, 1-20 (default: 5)
serveroptionalDNS server for dns lookups

Examples

# Ping
curl -X POST "http://192.168.1.100/banana/network_tools?tool=ping&target=8.8.8.8&count=3"

# DNS lookup
curl -X POST "http://192.168.1.100/banana/network_tools?tool=dns&target=google.com"

# Traceroute
curl -X POST "http://192.168.1.100/banana/network_tools?tool=traceroute&target=cloudflare.com"

# ARP table with vendor lookup
curl -X POST "http://192.168.1.100/banana/network_tools?tool=arp"

# WHOIS / IP geolocation
curl -X POST "http://192.168.1.100/banana/network_tools?tool=iplookup&target=8.8.8.8"

# Get public IP
curl -X POST "http://192.168.1.100/banana/network_tools?tool=publicip"

Ping Response

{
  "status": "success",
  "output": "PING 8.8.8.8 ...",
  "stats": {
    "transmitted": 3, "received": 3, "loss": 0,
    "rtt_min": "21.5", "rtt_avg": "24.2", "rtt_max": "28.1"
  }
}
POST /banana/run_speedtest Run a full speed test (30-60 seconds)

Parameters

None

Response

{
  "status": "success",
  "download_mbps": 146,
  "upload_mbps": 89,
  "ping_ms": 12,
  "jitter_ms": 3,
  "server": "Chicago (ORD)",
  "sponsor": "",
  "isp": "",
  "provider": "cloudflare"
}
This is a blocking call that takes 30-60 seconds. The speed test measures the NOTBOX uplink, not the device under test. Throughput values are rounded to whole Mbps. server is the nearest Cloudflare edge (City (POP-code)); sponsor and isp are not reported by Cloudflare and return empty.
POST /banana/test_port Test TCP port connectivity

Parameters

NameRequiredDescription
targetrequiredHostname or IP
portrequiredPort number (1-65535)

Example

curl -X POST "http://192.168.1.100/banana/test_port?target=google.com&port=443"
{
  "status": "success",
  "result": "open",
  "response_time_ms": 45,
  "target": "google.com",
  "port": 443,
  "ip": "142.251.33.14"
}

WiFi Management

POST /banana/configure_wifi Enable and configure the WiFi test AP

Parameters

NameRequiredDefaultDescription
ssidoptionalNetTest-ImpairedNetwork name (1-32 chars)
passwordoptionalWiFi password (8-63 chars, required unless encryption=none)
bandoptional2g2g or 5g
encryptionoptionalpsk2none, psk, psk2, psk-mixed, sae (WPA3), sae-mixed
channeloptionalautoChannel number or auto

Example

curl -X POST -d "ssid=Example-Test-AP&password=example-password&band=5g&encryption=sae" "http://192.168.1.100/banana/configure_wifi"
{
  "status": "success",
  "ssid": "Example-Test-AP",
  "band": "5GHz",
  "interface": "wifi-5g"
}
WiFi AP is bridged into the impairment path. Devices connected via WiFi experience the same download impairments as wired devices.
POST /banana/stop_wifi Disable the WiFi test AP

Parameters

None

Response

{ "status": "success" }
GET /banana/get_wifi_status Check WiFi AP status

Parameters

None

Response

{
  "status": "active",
  "ssid": "Example-Test-AP",
  "band": "5GHz",
  "interface": "wifi-5g"
}
Returns "status": "inactive" when WiFi is off.
GET /banana/get_wifi_clients List connected WiFi clients

Parameters

None

Response

{
  "clients": [
    { "mac": "aa:bb:cc:dd:ee:ff", "signal": "-42 dBm", "connected": "15m" }
  ]
}

Network Storage (SMB / NFS)

POST /banana/mount_smb Mount a network share for capture storage

Parameters

NameRequiredDescription
hostrequiredSMB server hostname or IP
sharerequiredShare name
useroptionalUsername for auth
passwordoptionalPassword for auth
domainoptionalDomain for auth
pathoptionalSubfolder within share

Response

{
  "status": "success",
  "message": "Connected and verified",
  "mount_point": "/mnt/capture_share",
  "share": "//example-fileserver/captures",
  "available_space": "500G"
}
Tries SMB 3.0, 2.1, 2.0 in order. Uses MGMT interface to avoid mixing SMB traffic with captures. Guest access supported.
POST /banana/unmount_smb Disconnect network share

Parameters

None

Response

{ "status": "success", "message": "Disconnected" }
Will refuse to unmount if a capture is currently running.
POST /banana/mount_nfs Mount an NFS export for capture storage

Parameters

NameRequiredDescription
hostrequiredNFS server hostname or IP
exportrequiredNFS export path (for example /srv/notbox-pcaps)
pathoptionalSubfolder within the mounted export
versionoptionalauto (default), 4.2, 4.1, 4.0, or 3
optionsoptionalAdditional mount options (advanced)

Response

{
  "status": "success",
  "message": "Connected and verified",
  "mount_point": "/mnt/capture_share",
  "capture_dir": "/mnt/capture_share/site-a",
  "share": "192.168.1.50:/srv/notbox-pcaps",
  "available_space": "500G",
  "protocol": "nfs"
}
Uses MGMT interface for share access. In auto mode, NOTBOX tries NFS versions 4.2, 4.1, 4.0, then 3.
POST /banana/unmount_nfs Disconnect NFS network share

Parameters

None

Response

{ "status": "success", "message": "Disconnected" }
Will refuse to unmount if a capture is currently running.
GET /banana/check_smb_mount Check network share status

Parameters

None

Response

{
  "status": "connected",
  "share": "//example-fileserver/captures",
  "available_space": "500G",
  "used_space": "250G",
  "writable": true,
  "capture_count": 5
}
Returns "status": "disconnected" when no share is mounted.
GET /banana/check_nfs_mount Check NFS network share status

Parameters

None

Response

{
  "status": "connected",
  "share": "192.168.1.50:/srv/notbox-pcaps",
  "capture_dir": "/mnt/capture_share/site-a",
  "available_space": "500G",
  "used_space": "250G",
  "writable": true,
  "capture_count": 5,
  "protocol": "nfs"
}
Returns {"status":"disconnected"} when no NFS share is mounted.

System & Configuration

GET /banana/get_system_info CPU, RAM, disk, temperature, uptime, throughput

Parameters

None

Response

{
  "cpu": "42",
  "ram_used": "69",
  "ram_total": "234",
  "disk_free": "82.8",
  "tmp_used": "0.2",
  "tmp_total": "117.0",
  "temp": "60",
  "uptime": "5d 3h 45m",
  "rx_rate": "5.2 Mbps",
  "tx_rate": "1.3 Mbps",
  "device_unix": 1771819200,
  "device_time_utc": "2026-02-23T20:00:00Z",
  "timezone": "EST5EDT,M3.2.0,M11.1.0",
  "zonename": "America/New_York"
}
CPU is percentage. RAM/disk in MB. Temp in Celsius. RX/TX measured on the test bridge. Clock/timezone fields are included for NTP and timezone troubleshooting.
GET /banana/get_serial Get the Serial that matches the sticker on your device

Parameters

None

Response

{
  "serial": "NB20260412065"
}
This is the value to use when contacting support or recording inventory. If the device hasn't yet been provisioned with a Serial, the response is {"serial": null}.
GET /banana/get_network_config Get management network configuration

Parameters

None

Response

{
  "status": "success",
  "hostname": "NOTBOX",
  "ip_mode": "dhcp",
  "ip_addr": "",
  "netmask": "",
  "gateway": "",
  "dns": "",
  "current_ip": "192.168.1.100",
  "current_gateway": "192.168.1.1"
}
POST /banana/set_network_config Change management IP, hostname, DNS

Parameters

NameRequiredDescription
hostnameoptionalDevice hostname (1-63 chars)
ip_modeoptionaldhcp or static
ip_addrif staticStatic IP address
netmaskoptionalSubnet mask (default: 255.255.255.0)
gatewayoptionalGateway IP
dnsoptionalDNS server IP

Response

{
  "status": "success",
  "message": "Settings saved. Network will restart.",
  "new_ip": "192.168.1.100",
  "needs_reload": true
}
Network restarts after a 2-second delay. If you change the IP, you'll need to reconnect to the new address.
GET /banana/get_mgmt_mode Check connectivity mode and available features

Parameters

None

Response

{
  "status": "success",
  "mode": "full",
  "internet": true,
  "dns_ok": true,
  "connection": "network",
  "connection_desc": "Connected via network (MGMT)",
  "mgmt_link": 1,
  "mgmt_ip": "192.168.1.100",
  "local_link": 1,
  "local_ip": "192.168.100.1",
  "disabled_features": []
}
mode is "full" with internet access or "limited" without. Limited mode disables speedtest, uplink monitor, external port tests, and network captures.
GET /banana/check_network_interfaces List all network interfaces and connectivity

Parameters

None

Response

{
  "mgmt_exists": true,
  "mgmt_ip": "192.168.1.100/24",
  "ping_success": true,
  "default_gateway": "192.168.1.1"
}

Authentication

GET /banana/check_auth Check if authentication is required/active

Parameters

None (reads notbox_session cookie automatically)

Response

{
  "auth_required": true,
  "is_authorized": false
}
When auth_required is false, all endpoints are open. When true, authenticate with either a session cookie (from /banana/login) or a Bearer token (from /banana/generate_api_token). Bearer tokens are recommended for scripts: curl -H "Authorization: Bearer <example-token>"
POST /banana/login Authenticate and get a session cookie

Parameters (POST body)

NameRequiredDescription
passwordrequiredDevice password

Example

curl -c cookies.txt -d "password=example-password" "http://192.168.1.100/banana/login"
{
  "status": "success",
  "message": "Authenticated successfully"
}
Use -c cookies.txt to save the session cookie, then -b cookies.txt on subsequent requests.
POST /banana/logout End the current session

Parameters

None

Response

{ "status": "success", "message": "Logged out successfully" }
POST /banana/set_password Enable, disable, or change device password

Parameters

NameRequiredDescription
actionrequiredQuery param: enable, disable, or change
new_passwordfor enable/changeNew password (min 4 chars, POST body)
current_passwordfor disable/changeCurrent password (POST body)

Example

# Enable password protection
curl -d "new_password=example-password" "http://192.168.1.100/banana/set_password?action=enable"
{ "status": "success", "message": "Password protection enabled" }
GET /banana/get_password_status Check if password protection is enabled

Parameters

None

Response

{ "enabled": false }
POST /banana/generate_api_token Generate an API access token for scripts

Parameters

None (requires password protection to be enabled and an active session)

Example

curl -X POST -b cookies.txt "http://192.168.1.100/banana/generate_api_token"
{
  "status": "success",
  "token": "0123456789abcdef0123456789abcdef"
}
Use the returned token in subsequent requests: curl -H "Authorization: Bearer <example-token>". Only one token exists at a time. Generating a new one replaces the previous. Token is revoked when password is changed or disabled.
GET /banana/get_api_token Retrieve the current API token

Parameters

None (requires active session)

Response

{
  "exists": true,
  "token": "0123456789abcdef0123456789abcdef"
}
POST /banana/revoke_api_token Revoke the current API token

Parameters

None (requires active session)

Response

{ "status": "success", "message": "API token revoked" }
Any scripts using the revoked token will immediately lose access.

Firmware Updates

POST /banana/check_update Check if a firmware update is available

Parameters

NameRequiredDescription
channeloptionalstable (default) or pinned/specific
requested_versionpinned onlyExact version to check (for example 1.0.2)
actionoptionalcheck (default) or list_versions (returns versions for the UI "Specific Version" selector)

Response

{
  "status": "success",
  "channel": "stable",
  "update_available": true,
  "latest_version": "1.0.2",
  "release_notes": "Stable release. Live capture summary, ECharts visuals, hardened OTA signing...",
  "sha256": "<sha256-from-server>",
  "file_size": 13875482,
  "current_version": "1.0.1"
}
For action=list_versions, the response includes versions[] instead of the standard update-availability fields.
POST /banana/apply_update Download, apply, check status, or cancel firmware updates

Parameters

NameRequiredDescription
actionrequireddownload, apply, status, or cancel
versionrequired*Version to download (e.g. 1.0.2). Required when action=download.
channelfor downloadstable or pinned. Used for server-side channel validation and file selection.
sha256for downloadExpected checksum for verification

Example: Full Update Flow

# 1. Check for updates
curl -X POST "http://192.168.1.100/banana/check_update"

# 2. Download firmware
curl -X POST "http://192.168.1.100/banana/apply_update?action=download&version=1.0.2&channel=stable&sha256=<sha256-from-check_update>"

# 3. Apply update (device reboots)
curl -X POST "http://192.168.1.100/banana/apply_update?action=apply"

# 4. Wait ~90 seconds, then check status
curl -X POST "http://192.168.1.100/banana/apply_update?action=status"
The apply action triggers a full firmware flash and reboot. The device will be unavailable for 1-2 minutes. Do not power off during the update.

Scripting Examples

Bash: Latency Sweep Test

#!/bin/bash
# Test app behavior across increasing latency values
NOTBOX="http://192.168.1.100"
TOKEN="example-token"  # Optional: only needed if password is enabled
AUTH="-H \"Authorization: Bearer ${TOKEN}\""

for latency in 0 25 50 100 200 500; do
    echo "Testing with ${latency}ms latency..."
    curl -s -X POST ${AUTH} "${NOTBOX}/banana/set_impairment?interface=download&delay=${latency}" > /dev/null
    sleep 2

    # Run your test suite here
    # ./run-tests.sh

    echo "  Latency test at ${latency}ms complete"
done

# Clean up
curl -s -X POST ${AUTH} "${NOTBOX}/banana/clear_impairment?interface=download" > /dev/null
echo "Done - impairments cleared"

Python: Automated Impairment + Capture

import requests, time, json

NOTBOX = "http://192.168.1.100"
HEADERS = {"Authorization": "Bearer example-token"}  # Optional: only if password enabled

# Set impairment
requests.post(f"{NOTBOX}/banana/set_impairment", headers=HEADERS, params={
    "interface": "download",
    "delay": "100",
    "loss": "5",
    "bandwidth": "10",
    "bandwidth_unit": "mbit"
})

# Start capture
r = requests.post(f"{NOTBOX}/banana/start_capture", headers=HEADERS, params={
    "duration": "30",
    "proto_filter": "either:https"
})
filename = r.json()["filename"]

# Wait for capture to finish
time.sleep(35)

# Summarize the capture (sub-millisecond)
summary = requests.post(f"{NOTBOX}/banana/get_live_stats",
    headers=HEADERS, params={"filename": filename}).json()

print(f"Packets: {summary['packet_count']}")
print(f"Download: {summary['download_bytes']} bytes")
print(f"Top destinations:")
for dest in summary["top_dest"][:5]:
    print(f"  {dest['name'] or dest['ip']}: {dest['bytes']} bytes")

# Clean up
requests.post(f"{NOTBOX}/banana/clear_impairment",
    headers=HEADERS, params={"interface": "download"})
requests.post(f"{NOTBOX}/banana/delete_capture",
    headers=HEADERS, params={"filename": filename})

CI/CD: Pre-Deploy Network Check

#!/bin/bash
# Verify app handles poor network conditions before deploy
NOTBOX="http://192.168.1.100"
AUTH="-H \"Authorization: Bearer ${NOTBOX_TOKEN}\""  # Set NOTBOX_TOKEN in CI secrets

# Simulate a degraded network
curl -s -X POST ${AUTH} "${NOTBOX}/banana/set_impairment?interface=both&delay=200&loss=3"

# Run integration tests against device under test
npm run test:integration
TEST_EXIT=$?

# Always clean up
curl -s -X POST ${AUTH} "${NOTBOX}/banana/clear_impairment?interface=both"

exit $TEST_EXIT