A small set of Python utilities for discovering Axis network cameras on a local subnet and querying them via VAPIX.
Built to be practical on modern AXIS OS 12.x, where multicast discovery and older app-config endpoints may be disabled.
- Features
- Repository contents
- Requirements
- Usage
- Example output
- How it works
- Notes & limitations
- Contributing
- License
-
Subnet discovery (no SSDP required)
Scans an IP range and identifies Axis devices by calling the VAPIX Basic Device Info API. -
Device inventory output
For each discovered device, prints:- Hostname
- IP address
- Device type
- Model
- Product name
- Serial number
- AXIS OS / firmware version
-
ACAP app visibility
- Lists installed ACAP applications
- Shows whether each app is Running / Stopped / Idle
- Pulls app settings using the VAPIX Parameter API and prints them per‑app
-
Progress bar during subnet scan (optional, via
tqdm)
-
discover_axis_vapix.pyMain discovery tool (subnet scan + device info + apps + params). -
acap_update_app.pyBatch ACAP updater that stops/removes an existing package, uploads a new.eap, and restarts the app across single IPs or whole subnets. -
acap_update_config.pyStops a running app, applies parameter changes from a config file, and restarts the app without uploading a new.eap. -
overlay.pyDynamic Overlay helper that can run fully from the CLI or through an interactive menu to calldynamicoverlay.cgimethods, including dynamic text slot actions (dtext-gettext,dtext-settext). -
discover_axis_vapix screenshot.jpg
Example output screenshot.
- Python 3.8+
- Packages:
requeststqdm(optional but recommended for progress bar)
Install deps:
pip install requests tqdmRun a scan on your local subnet:
python discover_axis_vapix.py --subnet 192.168.1.0/24 --user root --passw "yourPassword"-
--subnet
CIDR subnet to scan. Example:192.168.1.0/24 -
--user/--passw
Admin credentials. Recommended, and required to list ACAP apps + settings. -
--workers
Number of parallel probe threads (default is high for quick LAN scans). -
--timeoutHTTP request timeout per host. -
--param-timeoutTimeout for the full parameter dump (used to find app settings).
Update a single camera:
python acap_update_app.py --ip 192.168.1.50 --user root --passw "yourPassword" --package MyAcap --eap /path/to/MyAcap.eapUpdate every camera in a subnet (in parallel):
python acap_update_app.py --subnet 192.168.1.0/24 --user root --passw "yourPassword" --package MyAcap --eap /path/to/MyAcap.eap --workers 32Parameters:
-
--ipSingle camera IP. Use this or--subnet. -
--subnetCIDR range to update (e.g.,192.168.1.0/24). -
--user/--passwAdmin credentials used for VAPIX upload/control calls. -
--packagePackageNamevalue as reported byapplications/list.cgi. -
--eapPath to the.eapfile to upload. -
--configOptional path to a param config file (key=value per line) to apply after uploading. -
--workersParallel workers when running in subnet mode (default: 16). -
--no-stopSkip the pre-uploadstopcall (still attempts remove/upload/start). -
--usergroupUser group to include inparam.cgiupdates when supplying a config file. -
--prefer-httpPrefer HTTP when both 80 and 443 are open (HTTPS remains default). -
--force-http/--force-httpsForce a specific scheme when probing cameras.
Update a single camera's app configuration without uploading a new package:
python acap_update_config.py --ip 192.168.1.50 --user root --passw "yourPassword" --package MyAcap --config ./config.txtUpdate a whole subnet in parallel:
python acap_update_config.py --subnet 192.168.1.0/24 --user root --passw "yourPassword" --package MyAcap --config ./config.txt --workers 32Parameters:
-
--ipSingle camera IP. Use this or--subnet. -
--subnetCIDR range to update (e.g.,192.168.1.0/24). -
--user/--passwAdmin credentials used for VAPIX control and parameter calls. -
--packagePackageNamevalue as reported byapplications/list.cgi. -
--configRequired path to the param config file (key=value per line). -
--workersParallel workers when running in subnet mode (default: 16). -
--no-stopSkip stopping the app before applying the config (defaults to stopping first). -
--usergroupUser group to include inparam.cgiupdates. -
--prefer-httpPrefer HTTP when both 80 and 443 are open (HTTPS remains default). -
--force-http/--force-httpsForce a specific scheme when probing cameras.
Call the Dynamic Overlay API either directly from the CLI or with a guided menu if you omit the method/IP flags.
- Auto-detects HTTP/HTTPS (prefers HTTPS if port 443 is open).
- Uses Basic or Digest auth automatically (
curl --anyauthequivalent). - Optional
--json-onlyflag prints only the JSON response (no payload preview or curl helper) for scripting. - Covers all Dynamic Overlay and upload overlay image methods:
getSupportedVersions: fetch supported API versions (noapiVersionor params required).getOverlayCapabilities: returns overlay feature capabilities for the device.list: returns existing overlays.addText: create a text overlay (text normalized to%0Afor newlines).setText: update an existing text overlay.dtext-settext/dtext-gettext: call the dynamic text slot API (settext/gettext) without acontextvalue.addImage: create an image overlay.setImage: update an existing image overlay.remove: delete an overlay by identity.uploadOverlayImage: upload an image file before assigning it to an overlay.listImages: list previously uploaded overlay images.deleteImage: remove a previously uploaded overlay image.
- Method-aware prompts in the interactive menu:
addText/setText: guided prompts for camera number, position, colors, and text (newlines normalized to%0A).addImage: requests camera number, image path, and position.setImage: asks for overlay identity plus optional overlay path and/or position updates.getSupportedVersions: sends the request with only the method plus optional context (noapiVersionorparams).dtext-settext/dtext-gettext: prompt only for the text slot index (and text for updates) and skipcontextsince the camera ignores it.list: sends the request without asking for parameters (only the optional context).listImages: sends the request without asking for parameters.remove: prompts only for overlay identity (with optional context echo).- Other methods accept a single round of key=value pairs when parameters are needed.
- Shows the JSON payload and the equivalent curl command before sending the request.
Run fully from the CLI:
python overlay.py --ip 192.168.1.185 --user root --passw "SuperSecurePass" \
--method addText --param camera=1 --param position=topLeft \
--param text="Hello There!" --param fontSize=18 --param textColor=whiteExample output (payload + curl helper + JSON response):
--- Request ---
{
"method": "addText",
"apiVersion": "1.0",
"params": {
"camera": 1,
"position": "topLeft",
"text": "Hello There!",
"fontSize": 18,
"textColor": "white"
}
}
Equivalent curl command:
curl --anyauth -u "root:SuperSecurePass" -H "Content-Type: application/json" --data "{\"method\":\"addText\",\"apiVersion\":\"1.0\",\"params\":{\"camera\":1,\"position\":\"topLeft\",\"text\":\"Hello There!\",\"fontSize\":18,\"textColor\":\"white\"}}" https://192.168.1.185/axis-cgi/dynamicoverlay/dynamicoverlay.cgi -k
--- Response ---
{
"data": {
"identity": 2
},
"apiVersion": "1.0",
"context": null
}
Use the interactive menu when exploring capabilities or when you want prompts for parameters:
python overlay.pyYou will be asked for the camera IP, credentials, and a method selection (covering overlay and upload methods). Each choice then prompts for the relevant params before issuing the request and printing the JSON response. For image overlays, addImage requests the camera number, overlay image path, and desired position, while setImage prompts for the overlay identity and any updates to overlayPath or position. listImages runs without asking for any parameters.
- Use the
uploadOverlayImagemethod to push an image file to the camera before referencing it inaddImage/setImage. - Supply
--image-file /path/to/file.pngon the CLI (or provide a path when prompted in the menu). Optional params such asscaleToResolutionandalphaare supported when you add--paramentries. - After upload, call
listImagesto enumerate stored files ordeleteImageto remove one.
-
Text overlays support Axis overlay text modifiers, so you can embed runtime values such as date/time or device data in the string you pass to
addText/setText. See the overlay modifiers schema for the full list. -
Dynamic text slots let you change part of the overlay without recreating it. Place
#D<SLOT>tokens inside thetextparameter (for example,"Camera #n – #D1"). -
Update or read a slot value through the dynamic text endpoint on the camera, e.g.
https://<camera>/axis-cgi/dynamicoverlay/dynamicoverlay.cgi?action=setDynamicText&slot=1&text=Helloto set slot 1, and...action=getDynamicText&slot=1to fetch the current string. The overlay updates automatically wherever#D1appears. More details are in the Dynamic Text documentation. -
Use the dedicated CLI methods
dtext-settextanddtext-gettextto invoke those slot updates directly. For example:python overlay.py --ip 192.168.1.185 --user root --passw "SuperSecurePass" \ --method dtext-settext --param text_index=2 --param text="Joe" python overlay.py --ip 192.168.1.185 --user root --passw "SuperSecurePass" \ --method dtext-gettext --param text_index=2
-
Dynamic text slot actions do not accept a
contextvalue (the camera ignores it for these GET requests). -
The CLI names include a
dtext-prefix to distinguish them from the main overlaysetText/addTextmethods. -
The script normalizes newlines to
%0Aso multi-line text is rendered correctly by the camera.
Use setImage to update properties for an existing image overlay. Required and optional parameters mirror the Axis Dynamic Overlay API:
identity(integer, required): which overlay to change.overlayPath(string, optional): path to the image file that should be displayed for the overlay.position(predefined keyword or[x,y]array, optional): accepts one oftop,topRight,bottomRight,bottom,bottomLeft,topLeft, or a relative coordinate pair ranging from-1.0to1.0for both X and Y. If omitted, the position remains unchanged.context(string, optional): echoed back in the response when supplied.
Example request body:
{
"apiVersion": "1.0",
"context": "444",
"method": "setImage",
"params": {
"identity": 3,
"overlayPath": "/usr/local/images/logo.png",
"position": "topRight"
}
}
Successful responses include the echoed context (if present) and an empty data object:
{
"apiVersion": "1.0",
"method": "setImage",
"context": "444",
"data": {}
}
Per‑device app/config listing first, then summary table:
== P1465-LE-LAB 192.168.1.185 (P1465-LE) ==
- FireXXXXXXX v0.1.0 -> Running
params:
- ConfFireThresholdPercent = 75
- ConfSmokeThresholdPercent = 65
- LightstackIp = 192.168.1.233
HostName | IP | Type | Model | Name | Serial | OS Version | Apps
-------------+---------------+---------------+----------+-----------------------------+--------------+------------+------------------------
P1465-LE-LAB | 192.168.1.185 | Bullet Camera | P1465-LE | AXIS P1465-LE Bullet Camera | B8A44FE6XXXX | 12.6.97 | 4 installed (1 running)
-
Probe each IP in the subnet for port 80/443.
-
If open, call:
Basic Device Information API
POST /axis-cgi/basicdeviceinfo.cgi
Used to identify Axis devices and fetch inventory fields. -
For each Axis camera (with admin creds):
Application API
POST /axis-cgi/applications/list.cgi
Returns installed ACAP apps + running state. -
To find app settings on AXIS OS 12.x:
Parameter API (legacy CGI)
GET /axis-cgi/param.cgi?action=list
The tool downloads the full parameter tree once per camera and filters keys that match app name/vendor tokens, then prints them under each app.
-
SSDP/UPnP discovery is not used
AXIS OS 12.x often disables multicast discovery by default, so subnet scan is the most reliable method. -
App settings are heuristic‑matched
Some 3rd‑party ACAPs do not expose configuration through VAPIX parameters; these will show “no matching keys found.” -
Non‑standard ports
If your cameras run HTTP/HTTPS on ports other than 80/443, you’ll need to extend the probe logic. -
Credentials
Without admin credentials you’ll still get device inventory, but apps/settings will be skipped.
Issues and PRs are welcome. If you add support for:
- alternate VAPIX endpoints
- non‑80/443 ports
- better per‑app settings discovery
please open a pull request.
MIT License © 2025 Joseph Sammarco. See LICENSE.
