ax-check.com
by Gauge
P
Palo Alto Networks
paloaltonetworks.com
ax-check.com
Checked 5 November 2026
B
76/100
Agents need your login
Docs guide agents well, but pricing stays a guessing game
The homepage links to a well-organized docs index and quickstart, but no pricing page was ever found, forcing agents to estimate costs from outside knowledge.
2 fixes to start with
Concrete changes, backed by what we found.
Recorded coding sessions
Same task, 3 models, no paloaltonetworks.com account. Recorded 5 November 2026 on Pi.
3 recorded · 1m 25s
1m 25s · recorded
Session findings
Select a finding to see it in context.
›Thinking…
The user is asking for a "simple example using Palo Alto Networks." That's vague — Palo Alto Networks has lots of products. Let me check the repo context first.
Let me look at the working directory.
pwd; ls -la; cat README* 2>/dev/null | head -50
/sandbox/repo
/sandbox/repo total 16 drwxr-xr-x 3 root root 4096 Sep 18 08:00 . drwxr-xr-x 7 root root 4096 Nov 5 16:04 .. drwxr-xr-x 8 root root 4096 Sep 24 20:05 .git -rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore
Recorded tool call
{
"type": "tool_call",
"seq": 3,
"ts": "2026-11-05T16:05:03.256Z",
"name": "bash",
"turn": 1,
"input": {
"command": "pwd; ls -la; cat README* 2>/dev/null | head -50"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-a15bfcb2c7f11bd1",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 5,
"ts": "2026-11-05T16:05:03.256Z",
"turn": 1,
"output": {
"content": [
{
"text": "/sandbox/repo\ntotal 16\ndrwxr-xr-x 3 root root 4096 Sep 18 08:00 .\ndrwxr-xr-x 7 root root 4096 Nov 5 16:04 ..\ndrwxr-xr-x 8 root root 4096 Sep 24 20:05 .git\n-rw-r--r-- 1 root root 408 Sep 18 08:00 .gitignore\n",
"type": "text"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-a15bfcb2c7f11bd1",
"is_error": false,
"tool_kind": "shell"
}env | grep -i -E "PI_|PAN|PALO|API_KEY|TOKEN" | sed 's/=.\{6,\}/=<redacted>/'PI_CODING_AGENT=true
PI_CODING_AGENT=true PI_REASONING_LEVEL=<redacted> PI_SESSION_FILE=<redacted> PI_PROVIDER=<redacted> PI_MODEL=<redacted> PI_SESSION_ID=<redacted> PI_GATEWAY_API_KEY=<redacted>
Recorded tool call
{
"type": "tool_call",
"seq": 4,
"ts": "2026-11-05T16:05:03.256Z",
"name": "bash",
"turn": 1,
"input": {
"command": "env | grep -i -E \"PI_|PAN|PALO|API_KEY|TOKEN\" | sed 's/=.\\{6,\\}/=<redacted>/'"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-9163bd725a247523",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 6,
"ts": "2026-11-05T16:05:03.256Z",
"turn": 1,
"output": {
"content": [
{
"text": "PI_CODING_AGENT=true\nPI_REASONING_LEVEL=<redacted>\nPI_SESSION_FILE=<redacted>\nPI_PROVIDER=<redacted>\nPI_MODEL=<redacted>\nPI_SESSION_ID=<redacted>\nPI_GATEWAY_API_KEY=<redacted>\n",
"type": "text"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-9163bd725a247523",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Empty repo. No Palo Alto credentials. Let me check network access and what "Palo Alto Networks" developer workflow means. PAN products: PAN-OS (firewall OS, pan-os-python SDK / REST API), Prisma Access, Cortex XSOAR, Strata Cloud Manager.
Key question: is there a developer workflow I can act on without credentials? There's pan-os-python SDK, and PAN-OS REST API. But there's no live firewall and no hosted sandbox API accessible without credentials.
Let me check if network access exists (pip install pan-os-python), and check whether there's any public API that works without credentials. Actually, PAN does have a free developer sandbox? PAN-OS has "Strata Cloud Manager" with API. There's also the PAN Developer API for threat intelligence... Let me test network reachability quickly.
cd /sandbox/repo && timeout 25 pip download pan-os-python -d /tmp/x --no-deps -q 2>&1 | tail -5; echo "---pypi exit $?"; timeout 20 curl -sS -o /dev/null -w "%{http_code}\n" https://pypi.org/simple/ 2>&1[notice] A new release of pip is available: 26.2.1 -> 26.3
[notice] A new release of pip is available: 26.2.1 -> 26.3 [notice] To update, run: /opt/freestyle/python/bin/python3 -m pip install --upgrade pip ---pypi exit 0 200
Recorded tool call
{
"type": "tool_call",
"seq": 9,
"ts": "2026-11-05T16:05:06.318Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && timeout 25 pip download pan-os-python -d /tmp/x --no-deps -q 2>&1 | tail -5; echo \"---pypi exit $?\"; timeout 20 curl -sS -o /dev/null -w \"%{http_code}\\n\" https://pypi.org/simple/ 2>&1"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-819094861147d33b",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 12,
"ts": "2026-11-05T16:05:11.662Z",
"turn": 1,
"output": {
"content": [
{
"text": "\n[notice] A new release of pip is available: 26.2.1 -> 26.3\n[notice] To update, run: /opt/freestyle/python/bin/python3 -m pip install --upgrade pip\n---pypi exit 0\n200\n",
"type": "text"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-819094861147d33b",
"is_error": false,
"tool_kind": "shell"
}timeout 20 curl -sS -o /dev/null -w "pan.dev:%{http_code}\n" https://pan.dev/ ; timeout 20 curl -sS -o /dev/null -w "api.scm:%{http_code}\n" https://api.scm.paloaltonetworks.com/ 2>&1Failedpan.dev:200
pan.dev:200 curl: (6) Could not resolve host: api.scm.paloaltonetworks.com api.scm:000 Command exited with code 6
Recorded tool call
{
"type": "tool_call",
"seq": 10,
"ts": "2026-11-05T16:05:06.318Z",
"name": "bash",
"turn": 1,
"input": {
"command": "timeout 20 curl -sS -o /dev/null -w \"pan.dev:%{http_code}\\n\" https://pan.dev/ ; timeout 20 curl -sS -o /dev/null -w \"api.scm:%{http_code}\\n\" https://api.scm.paloaltonetworks.com/ 2>&1"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-b186d5b679bf78c2",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 11,
"ts": "2026-11-05T16:05:06.663Z",
"turn": 1,
"output": {
"content": [
{
"text": "pan.dev:200\ncurl: (6) Could not resolve host: api.scm.paloaltonetworks.com\napi.scm:000\n\n\nCommand exited with code 6",
"type": "text"
}
],
"details": {}
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-b186d5b679bf78c2",
"is_error": true,
"tool_kind": "shell"
}›Thinking…
PyPI is reachable (I could download pan-os-python). pan.dev reachable. The actual PAN cloud API endpoint (api.scm.paloaltonetworks.com) does not resolve — so no live API access, and there are no credentials anyway.
So: the developer workflow exists (pan-os-python SDK, PAN-OS REST API, Strata Cloud Manager API), but I can't actually run it — no firewall, no API key, and the hosted endpoints are unreachable from this sandbox.
The user asked: "Let me know if you get blocked." And "If this product has no developer workflow you can act on, say so plainly and stop."
There IS a developer workflow, but I can't act on it live. What can I do? I can build a simple, working example script that's ready to run once credentials exist — that's acting on the workflow in the only way available. Let me do the light version: install pan-os-python, write a small example that uses the SDK, verify it imports and is syntactically valid, and document pricing.
Let me check the downloaded package and install it, and write a minimal example. Keep it light.
Let me check pan-os-python version and basic API shape.
cd /sandbox/repo && timeout 90 pip install -q pan-os-python 2>&1 | tail -3; python -c "import panos,pan.xpanos; print('panos', panos.__version__)"Failed[notice] A new release of pip is available: 26.2.1 -> 26.3
[notice] A new release of pip is available: 26.2.1 -> 26.3 [notice] To update, run: /opt/freestyle/python/bin/python3 -m pip install --upgrade pip Traceback (most recent call last): File "<string>", line 1, in <module> ModuleNotFoundError: No module named 'pan.xpanos' Command exited with code 1
Recorded tool call
{
"type": "tool_call",
"seq": 15,
"ts": "2026-11-05T16:05:14.658Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && timeout 90 pip install -q pan-os-python 2>&1 | tail -3; python -c \"import panos,pan.xpanos; print('panos', panos.__version__)\""
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-b33b537f62776104",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 18,
"ts": "2026-11-05T16:05:17.455Z",
"turn": 1,
"output": {
"content": [
{
"text": "\n[notice] A new release of pip is available: 26.2.1 -> 26.3\n[notice] To update, run: /opt/freestyle/python/bin/python3 -m pip install --upgrade pip\nTraceback (most recent call last):\n File \"<string>\", line 1, in <module>\nModuleNotFoundError: No module named 'pan.xpanos'\n\n\nCommand exited with code 1",
"type": "text"
}
],
"details": {}
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-b33b537f62776104",
"is_error": true,
"tool_kind": "shell"
}timeout 25 curl -sS https://pypi.org/pypi/pan-os-python/json | python -c "import json,sys; d=json.load(sys.stdin); print(d['info']['version'], d['info']['requires_python']); print(d['info']['summary'])"
1.13.1 !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7
1.13.1 !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7 Framework for interacting with Palo Alto Networks devices via API
Recorded tool call
{
"type": "tool_call",
"seq": 16,
"ts": "2026-11-05T16:05:14.658Z",
"name": "bash",
"turn": 1,
"input": {
"command": "timeout 25 curl -sS https://pypi.org/pypi/pan-os-python/json | python -c \"import json,sys; d=json.load(sys.stdin); print(d['info']['version'], d['info']['requires_python']); print(d['info']['summary'])\""
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-887d1b6c9c6aa31f",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 17,
"ts": "2026-11-05T16:05:15.483Z",
"turn": 1,
"output": {
"content": [
{
"text": "1.13.1 !=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7\nFramework for interacting with Palo Alto Networks devices via API\n",
"type": "text"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-887d1b6c9c6aa31f",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
pan-os-python 1.13.1 installed but pan.xpanos doesn't exist — in newer versions xpanos was moved to a separate package? Let me check.
Actually in pan-os-python >= 1.0, the pan module is bundled (pan/python). Let me check.
python -c "import panos, pan; print(panos.__file__); print(pan.__file__)"; python -c "import panos; from panos.firewall import Firewall; from panos.objects import AddressObject, SecurityRule; print('ok')"; pip show pan-os-python 2>/dev/null | head -8/opt/freestyle/python/lib/python3.12/site-packages/panos/__init__.py
/opt/freestyle/python/lib/python3.12/site-packages/panos/__init__.py /opt/freestyle/python/lib/python3.12/site-packages/pan/__init__.py Traceback (most recent call last): File "<string>", line 1, in <module> ImportError: cannot import name 'SecurityRule' from 'panos.objects' (/opt/freestyle/python/lib/python3.12/site-packages/panos/objects.py) Name: pan-os-python Version: 1.13.1 Summary: Framework for interacting with Palo Alto Networks devices via API Home-page: https://github.com/PaloAltoNetworks/pan-os-python Author: Palo Alto Networks Author-email: devrel@paloaltonetworks.com License: ISC Location: /opt/freestyle/python/lib/python3.12/site-packages
Recorded tool call
{
"type": "tool_call",
"seq": 21,
"ts": "2026-11-05T16:05:18.508Z",
"name": "bash",
"turn": 1,
"input": {
"command": "python -c \"import panos, pan; print(panos.__file__); print(pan.__file__)\"; python -c \"import panos; from panos.firewall import Firewall; from panos.objects import AddressObject, SecurityRule; print('ok')\"; pip show pan-os-python 2>/dev/null | head -8"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-a884e82019cd4b9f",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 22,
"ts": "2026-11-05T16:05:18.830Z",
"turn": 1,
"output": {
"content": [
{
"text": "/opt/freestyle/python/lib/python3.12/site-packages/panos/__init__.py\n/opt/freestyle/python/lib/python3.12/site-packages/pan/__init__.py\nTraceback (most recent call last):\n File \"<string>\", line 1, in <module>\nImportError: cannot import name 'SecurityRule' from 'panos.objects' (/opt/freestyle/python/lib/python3.12/site-packages/panos/objects.py)\nName: pan-os-python\nVersion: 1.13.1\nSummary: Framework for interacting with Palo Alto Networks devices via API\nHome-page: https://github.com/PaloAltoNetworks/pan-os-python\nAuthor: Palo Alto Networks\nAuthor-email: devrel@paloaltonetworks.com\nLicense: ISC\nLocation: /opt/freestyle/python/lib/python3.12/site-packages\n",
"type": "text"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-a884e82019cd4b9f",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
SecurityRule is in panos.policy in v1.x. Let me write the example accordingly and validate offline with mocks (can't hit a device).
Let me build:
- requirements.txt
- example:
panos_example.py— connect via API key, create address object + security rule, commit; plus a--dry-runmode that prints what it would do (so it's runnable without a firewall). - README.md with pricing notes and manageability assessment.
Keep it light. Validate the script runs in dry-run mode.
Check API: panos.policy.SecurityRule params.
python - <<'EOF' from panos.policy import SecurityRule from panos.objects import AddressObject from panos.firewall import Firewall print([a for a in SecurityRule._params][:20]) print(AddressObject._params) EOF
FailedTraceback (most recent call last):
Traceback (most recent call last): File "<stdin>", line 1, in <module> ModuleNotFoundError: No module named 'panos.policy' Command exited with code 1
Recorded tool call
{
"type": "tool_call",
"seq": 25,
"ts": "2026-11-05T16:05:21.095Z",
"name": "bash",
"turn": 1,
"input": {
"command": "python - <<'EOF'\nfrom panos.policy import SecurityRule\nfrom panos.objects import AddressObject\nfrom panos.firewall import Firewall\nprint([a for a in SecurityRule._params][:20])\nprint(AddressObject._params)\nEOF"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-ac6aff2fcc30b2eb",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 26,
"ts": "2026-11-05T16:05:21.095Z",
"turn": 1,
"output": {
"content": [
{
"text": "Traceback (most recent call last):\n File \"<stdin>\", line 1, in <module>\nModuleNotFoundError: No module named 'panos.policy'\n\n\nCommand exited with code 1",
"type": "text"
}
],
"details": {}
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-ac6aff2fcc30b2eb",
"is_error": true,
"tool_kind": "shell"
}›Thinking…
Hmm, pan-os-python 1.13.1 modules? Let me list.
ls /opt/freestyle/python/lib/python3.12/site-packages/panos/ /opt/freestyle/python/lib/python3.12/site-packages/pan/
/opt/freestyle/python/lib/python3.12/site-packages/pan/:
/opt/freestyle/python/lib/python3.12/site-packages/pan/: __init__.py __pycache__ afapi commit.py config.py http.py licapi rc.py wfapi.py xapi.py /opt/freestyle/python/lib/python3.12/site-packages/panos/: __init__.py __pycache__ base.py device.py errors.py firewall.py ha.py network.py objects.py panorama.py plugins.py policies.py predefined.py updater.py userid.py
Recorded tool call
{
"type": "tool_call",
"seq": 29,
"ts": "2026-11-05T16:05:24.727Z",
"name": "bash",
"turn": 1,
"input": {
"command": "ls /opt/freestyle/python/lib/python3.12/site-packages/panos/ /opt/freestyle/python/lib/python3.12/site-packages/pan/"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-94945432e39782b4",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 30,
"ts": "2026-11-05T16:05:24.727Z",
"turn": 1,
"output": {
"content": [
{
"text": "/opt/freestyle/python/lib/python3.12/site-packages/pan/:\n__init__.py\n__pycache__\nafapi\ncommit.py\nconfig.py\nhttp.py\nlicapi\nrc.py\nwfapi.py\nxapi.py\n\n/opt/freestyle/python/lib/python3.12/site-packages/panos/:\n__init__.py\n__pycache__\nbase.py\ndevice.py\nerrors.py\nfirewall.py\nha.py\nnetwork.py\nobjects.py\npanorama.py\nplugins.py\npolicies.py\npredefined.py\nupdater.py\nuserid.py\n",
"type": "text"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-94945432e39782b4",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
It's panos.policies. Good. Let me write the example.
I'll write a single script that works in dry-run (offline) mode and live mode.
python - <<'EOF' from panos.policies import SecurityRule from panos.objects import AddressObject print([p for p in SecurityRule._params]) print([p for p in AddressObject._params]) EOF
FailedTraceback (most recent call last):
Traceback (most recent call last): File "<stdin>", line 3, in <module> AttributeError: type object 'SecurityRule' has no attribute '_params' Command exited with code 1
Recorded tool call
{
"type": "tool_call",
"seq": 33,
"ts": "2026-11-05T16:05:26.616Z",
"name": "bash",
"turn": 1,
"input": {
"command": "python - <<'EOF'\nfrom panos.policies import SecurityRule\nfrom panos.objects import AddressObject\nprint([p for p in SecurityRule._params])\nprint([p for p in AddressObject._params])\nEOF"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-af6d16634da82e2f",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 34,
"ts": "2026-11-05T16:05:26.616Z",
"turn": 1,
"output": {
"content": [
{
"text": "Traceback (most recent call last):\n File \"<stdin>\", line 3, in <module>\nAttributeError: type object 'SecurityRule' has no attribute '_params'\n\n\nCommand exited with code 1",
"type": "text"
}
],
"details": {}
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-af6d16634da82e2f",
"is_error": true,
"tool_kind": "shell"
}›Thinking…
I need to check if the current version 1.x is using ParamPath or SdkElement, and look at the attributes accordingly.
python - <<'EOF' from panos.policies import SecurityRule import inspect print(inspect.signature(SecurityRule.__init__)) EOF
(self, *args, **kwargs)
(self, *args, **kwargs)
Recorded tool call
{
"type": "tool_call",
"seq": 37,
"ts": "2026-11-05T16:05:27.885Z",
"name": "bash",
"turn": 1,
"input": {
"command": "python - <<'EOF'\nfrom panos.policies import SecurityRule\nimport inspect\nprint(inspect.signature(SecurityRule.__init__))\nEOF"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-86098c3eabc7f362",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 38,
"ts": "2026-11-05T16:05:27.885Z",
"turn": 1,
"output": {
"content": [
{
"text": "(self, *args, **kwargs)\n",
"type": "text"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-86098c3eabc7f362",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
SDK style objects use _params differently in 1.x? Let me check base.py SdkElement for _param_path or use dir on an instance.
python - <<'EOF'
from panos.policies import SecurityRule
r = SecurityRule()
names = [n for n in dir(r) if not n.startswith('_')]
print([n for n in names if n in ('name','fromzone','tozone','source','destination','application','action','description','service','log_start','log_end','disabled','rule_type')])
print('type of source attr:', type(r.source), getattr(r,'source',None))
EOF['action', 'application', 'description', 'destination', 'disabled', 'fromzone', 'log_end', 'log_start', 'name', 'service', 'source', 'tozone']
['action', 'application', 'description', 'destination', 'disabled', 'fromzone', 'log_end', 'log_start', 'name', 'service', 'source', 'tozone'] type of source attr: <class 'list'> ['any']
Recorded tool call
{
"type": "tool_call",
"seq": 41,
"ts": "2026-11-05T16:05:29.775Z",
"name": "bash",
"turn": 1,
"input": {
"command": "python - <<'EOF'\nfrom panos.policies import SecurityRule\nr = SecurityRule()\nnames = [n for n in dir(r) if not n.startswith('_')]\nprint([n for n in names if n in ('name','fromzone','tozone','source','destination','application','action','description','service','log_start','log_end','disabled','rule_type')])\nprint('type of source attr:', type(r.source), getattr(r,'source',None))\nEOF"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-b9c048e887520c88",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 42,
"ts": "2026-11-05T16:05:29.775Z",
"turn": 1,
"output": {
"content": [
{
"text": "['action', 'application', 'description', 'destination', 'disabled', 'fromzone', 'log_end', 'log_start', 'name', 'service', 'source', 'tozone']\ntype of source attr: <class 'list'> ['any']\n",
"type": "text"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-b9c048e887520c88",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Good. Now write the example files. Keep light: one script + README.
Script: panos_demo.py
- Reads PAN_HOST, PAN_API_KEY (or PAN_USERNAME/PAN_PASSWORD), PAN_SERIOS (serial for Panorama) from env.
--dry-run(default when no creds): builds config tree locally and prints XML it would push.- Live: refresh system info, create AddressObject + SecurityRule under the firewall (or device group in Panorama), commit.
Let me verify dry-run works offline including generating XML via element_str() maybe.
File edits
1 file
Writtenpanos_demo.pyView changes
/sandbox/repo/panos_demo.py
Written content · previous contents were not recorded
#!/usr/bin/env python3"""Minimal Palo Alto Networks PAN-OS example using the official pan-os-python SDK.Creates two objects and one security rule, then commits:1. AddressObject demo-web-server (10.10.10.10/32)2. SecurityRule demo-allow-https any -> trust, tcp/443 allowUsage-----Dry run (no device, no credentials, fully offline -- the default):python panos_demo.pyLive against a firewall:export PAN_HOST=192.0.2.10export PAN_API_KEY=... # Device > Setup > Operations > API key managementpython panos_demo.py --liveLive against Panorama (managing a managed firewall / device group):export PAN_HOST=panorama.example.comexport PAN_API_KEY=...export PAN_DEVICE_GROUP=Demo-DG # optional, defaults to "shared"export PAN_SERIAL=012345678901 # required for commits via Panoramapython panos_demo.py --liveCredentials can also come from PAN_USERNAME / PAN_PASSWORD instead of an API key.Nothing is pushed unless --live is passed."""from __future__ import annotationsimport argparseimport osimport sysfrom panos.errors import PanDeviceErrorfrom panos.firewall import Firewallfrom panos.objects import AddressObjectfrom panos.panorama import Panoramafrom panos.policies import SecurityRuleADDRESS_NAME = "demo-web-server"ADDRESS_VALUE = "10.10.10.10/32"RULE_NAME = "demo-allow-https"def build_config(parent):"""Attach the demo objects to a device (or Panorama device group) object tree."""addr = AddressObject(ADDRESS_NAME, value=ADDRESS_VALUE)rule = SecurityRule(RULE_NAME,description="Demo rule created by panos_demo.py",fromzone=["any"],tozone=["trust"],source=[ADDRESS_NAME],destination=["any"],application=["web-browsing", "ssl"],service=["application-default"],action="allow",log_start=False,log_end=True,)parent.add(addr)parent.add(rule)return addr, ruledef summarize(addr: AddressObject, rule: SecurityRule) -> str:return (f" address {addr.name:18} {addr.value}\n"f" rule {rule.name:18} {rule.fromzone} -> {rule.tozone} "f"apps={rule.application} svc={rule.service} action={rule.action}")def dry_run() -> int:"""Render the config locally and print the XML that would be pushed."""fw = Firewall("127.0.0.1", api_username="dry-run", api_password="dry-run")addr, rule = build_config(fw)print("[dry-run] no device contacted; nothing committed.\n")print("Config that would be created:")print(summarize(addr, rule))print("\nRendered XML:")for obj in (addr, rule):print(obj.element_str())return 0def live() -> int:host = os.environ.get("PAN_HOST")api_key = os.environ.get("PAN_API_KEY")username = os.environ.get("PAN_USERNAME")password = os.environ.get("PAN_PASSWORD")if not host or not (api_key or (username and password)):print("Missing credentials. Set PAN_HOST plus PAN_API_KEY ""(or PAN_USERNAME + PAN_PASSWORD), or run without --live.",file=sys.stderr,)return 2kwargs = dict(api_key=api_key) if api_key else dict(api_username=username, api_password=password)serial = os.environ.get("PAN_SERIAL")is_panorama = bool(os.environ.get("PAN_DEVICE_GROUP")) or bool(serial)if is_panorama:device = Panorama(host, **kwargs)device.refresh_system_info()print(f"Connected to Panorama {host}, PAN-OS {device.version}")dg_name = os.environ.get("PAN_DEVICE_GROUP", "shared")if dg_name == "shared":parent = device.add(device.shared)else:parent = device.refresh_devices() and next(dg for dg in device.device_groups if dg.name == dg_name)parent.refresh()print(f"Target device group: {dg_name}")else:device = Firewall(host, **kwargs)device.refresh_system_info()print(f"Connected to firewall {host}, PAN-OS {device.version}")parent = deviceaddr, rule = build_config(parent)print("Pushing config:")print(summarize(addr, rule))for obj in (addr, rule):obj.create()print(f" created {obj.NAME}: {obj.name}")print("Committing...")device.commit(sync=True)print("Commit complete.")return 0def main() -> int:parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)parser.add_argument("--live",action="store_true",help="actually push and commit (requires PAN_HOST + PAN_API_KEY)",)args = parser.parse_args()if not args.live:return dry_run()try:return live()except PanDeviceError as exc:print(f"PAN-OS error: {exc}", file=sys.stderr)return 1if __name__ == "__main__":raise SystemExit(main())
Source: write · chatcmpl-tool-b8e88f81c1a153a5
Recorded tool call
{
"type": "tool_call",
"seq": 45,
"ts": "2026-11-05T16:05:38.346Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/panos_demo.py",
"content": "#!/usr/bin/env python3\n\"\"\"Minimal Palo Alto Networks PAN-OS example using the official pan-os-python SDK.\n\nCreates two objects and one security rule, then commits:\n 1. AddressObject demo-web-server (10.10.10.10/32)\n 2. SecurityRule demo-allow-https any -> trust, tcp/443 allow\n\nUsage\n-----\nDry run (no device, no credentials, fully offline -- the default):\n python panos_demo.py\n\nLive against a firewall:\n export PAN_HOST=192.0.2.10\n export PAN_API_KEY=... # Device > Setup > Operations > API key management\n python panos_demo.py --live\n\nLive against Panorama (managing a managed firewall / device group):\n export PAN_HOST=panorama.example.com\n export PAN_API_KEY=...\n export PAN_DEVICE_GROUP=Demo-DG # optional, defaults to \"shared\"\n export PAN_SERIAL=012345678901 # required for commits via Panorama\n python panos_demo.py --live\n\nCredentials can also come from PAN_USERNAME / PAN_PASSWORD instead of an API key.\nNothing is pushed unless --live is passed.\n\"\"\"\n\nfrom __future__ import annotations\n\nimport argparse\nimport os\nimport sys\n\nfrom panos.errors import PanDeviceError\nfrom panos.firewall import Firewall\nfrom panos.objects import AddressObject\nfrom panos.panorama import Panorama\nfrom panos.policies import SecurityRule\n\nADDRESS_NAME = \"demo-web-server\"\nADDRESS_VALUE = \"10.10.10.10/32\"\nRULE_NAME = \"demo-allow-https\"\n\n\ndef build_config(parent):\n \"\"\"Attach the demo objects to a device (or Panorama device group) object tree.\"\"\"\n addr = AddressObject(ADDRESS_NAME, value=ADDRESS_VALUE)\n rule = SecurityRule(\n RULE_NAME,\n description=\"Demo rule created by panos_demo.py\",\n fromzone=[\"any\"],\n tozone=[\"trust\"],\n source=[ADDRESS_NAME],\n destination=[\"any\"],\n application=[\"web-browsing\", \"ssl\"],\n service=[\"application-default\"],\n action=\"allow\",\n log_start=False,\n log_end=True,\n )\n parent.add(addr)\n parent.add(rule)\n return addr, rule\n\n\ndef summarize(addr: AddressObject, rule: SecurityRule) -> str:\n return (\n f\" address {addr.name:18} {addr.value}\\n\"\n f\" rule {rule.name:18} {rule.fromzone} -> {rule.tozone} \"\n f\"apps={rule.application} svc={rule.service} action={rule.action}\"\n )\n\n\ndef dry_run() -> int:\n \"\"\"Render the config locally and print the XML that would be pushed.\"\"\"\n fw = Firewall(\"127.0.0.1\", api_username=\"dry-run\", api_password=\"dry-run\")\n addr, rule = build_config(fw)\n\n print(\"[dry-run] no device contacted; nothing committed.\\n\")\n print(\"Config that would be created:\")\n print(summarize(addr, rule))\n print(\"\\nRendered XML:\")\n for obj in (addr, rule):\n print(obj.element_str())\n return 0\n\n\ndef live() -> int:\n host = os.environ.get(\"PAN_HOST\")\n api_key = os.environ.get(\"PAN_API_KEY\")\n username = os.environ.get(\"PAN_USERNAME\")\n password = os.environ.get(\"PAN_PASSWORD\")\n\n if not host or not (api_key or (username and password)):\n print(\n \"Missing credentials. Set PAN_HOST plus PAN_API_KEY \"\n \"(or PAN_USERNAME + PAN_PASSWORD), or run without --live.\",\n file=sys.stderr,\n )\n return 2\n\n kwargs = dict(api_key=api_key) if api_key else dict(\n api_username=username, api_password=password\n )\n serial = os.environ.get(\"PAN_SERIAL\")\n is_panorama = bool(os.environ.get(\"PAN_DEVICE_GROUP\")) or bool(serial)\n\n if is_panorama:\n device = Panorama(host, **kwargs)\n device.refresh_system_info()\n print(f\"Connected to Panorama {host}, PAN-OS {device.version}\")\n dg_name = os.environ.get(\"PAN_DEVICE_GROUP\", \"shared\")\n if dg_name == \"shared\":\n parent = device.add(device.shared)\n else:\n parent = device.refresh_devices() and next(\n dg for dg in device.device_groups if dg.name == dg_name\n )\n parent.refresh()\n print(f\"Target device group: {dg_name}\")\n else:\n device = Firewall(host, **kwargs)\n device.refresh_system_info()\n print(f\"Connected to firewall {host}, PAN-OS {device.version}\")\n parent = device\n\n addr, rule = build_config(parent)\n print(\"Pushing config:\")\n print(summarize(addr, rule))\n for obj in (addr, rule):\n obj.create()\n print(f\" created {obj.NAME}: {obj.name}\")\n\n print(\"Committing...\")\n device.commit(sync=True)\n print(\"Commit complete.\")\n return 0\n\n\ndef main() -> int:\n parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)\n parser.add_argument(\n \"--live\",\n action=\"store_true\",\n help=\"actually push and commit (requires PAN_HOST + PAN_API_KEY)\",\n )\n args = parser.parse_args()\n\n if not args.live:\n return dry_run()\n try:\n return live()\n except PanDeviceError as exc:\n print(f\"PAN-OS error: {exc}\", file=sys.stderr)\n return 1\n\n\nif __name__ == \"__main__\":\n raise SystemExit(main())\n"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-b8e88f81c1a153a5",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 46,
"ts": "2026-11-05T16:05:38.346Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 5029 bytes to /sandbox/repo/panos_demo.py",
"type": "text"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-b8e88f81c1a153a5",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Wait, the Panorama device-group lookup line is ugly/wrong. Let me fix it. Panorama().device_groups exists after refresh_devices? Let me check the API. Actually in pan-os-python, pano.refresh_devices() returns nothing; device groups are refreshed via pano.refresh_devices(device_groups=True)? Let's check quickly.
Also device.add(device.shared) — shared is a SharedObject instance in panorama.py? Let me check.
python - <<'EOF'
from panos.panorama import Panorama, DeviceGroup
import inspect
print(inspect.signature(Panorama.refresh_devices))
p = Panorama("1.2.3.4", api_key="x")
print(hasattr(p, 'shared'), type(getattr(p,'shared',None)))
EOF(self, devices=(), only_connected=False, expand_vsys=True, include_device_groups=True, add=False, running_config=False)
(self, devices=(), only_connected=False, expand_vsys=True, include_device_groups=True, add=False, running_config=False) False <class 'NoneType'>
Recorded tool call
{
"type": "tool_call",
"seq": 49,
"ts": "2026-11-05T16:05:40.427Z",
"name": "bash",
"turn": 1,
"input": {
"command": "python - <<'EOF'\nfrom panos.panorama import Panorama, DeviceGroup\nimport inspect\nprint(inspect.signature(Panorama.refresh_devices))\np = Panorama(\"1.2.3.4\", api_key=\"x\")\nprint(hasattr(p, 'shared'), type(getattr(p,'shared',None)))\nEOF"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-a08764d03d45a12b",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 50,
"ts": "2026-11-05T16:05:40.427Z",
"turn": 1,
"output": {
"content": [
{
"text": "(self, devices=(), only_connected=False, expand_vsys=True, include_device_groups=True, add=False, running_config=False)\nFalse <class 'NoneType'>\n",
"type": "text"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-a08764d03d45a12b",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
So shared attribute doesn't exist on Panorama. In pan-os-python, shared objects are added by adding to panorama directly with pano.add(obj) — the SDK handles shared scope when parent is Panorama. Let me verify: in panorama.py, objects added directly to Panorama go to shared. Yes, in pan-os-python, adding an object to a Panorama instance puts it in shared.
And device groups: refresh_devices(include_device_groups=True, add=True) then pano.findall(DeviceGroup) or pano.find(dg_name, DeviceGroup).
Let me rewrite the live() Panorama section cleanly.
File edits
1 file
This tool call failed. Listed edits may not have been applied.
Modifiedpanos_demo.py+10−7View changes
/sandbox/repo/panos_demo.py
Replacement excerpt
if is_panorama:device = Panorama(host, **kwargs)device.refresh_system_info()print(f"Connected to Panorama {host}, PAN-OS {device.version}")dg_name = os.environ.get("PAN_DEVICE_GROUP", "shared")if dg_name == "shared":- parent = device.add(device.shared)+ parent = device # objects added straight to Panorama land in "shared"else:- parent = device.refresh_devices() and next(- dg for dg in device.device_groups if dg.name == dg_name- )+ device.refresh_devices(include_device_groups=True, add=True)+ parent = device.find(dg_name, DeviceGroup)+ if parent is None:+ print(f"Device group {dg_name!r} not found", file=sys.stderr)+ return 2parent.refresh()- print(f"Target device group: {dg_name}")+ print(f"Target scope: {dg_name}")
Replacement excerpt
-from panos.panorama import Panorama+from panos.panorama import DeviceGroup, Panorama
Replacement excerpt
- device = Firewall("127.0.0.1", api_username="dry-run", api_password="dry-run")+ fw = Firewall("127.0.0.1", api_username="dry-run", api_password="dry-run")+ fw.version = (11, 0, 0) # avoids an API call when rendering XML
Source: edit · chatcmpl-tool-9f774a621aacdb9b
Recorded tool call
{
"type": "tool_call",
"seq": 53,
"ts": "2026-11-05T16:05:45.346Z",
"name": "edit",
"turn": 1,
"input": {
"path": "/sandbox/repo/panos_demo.py",
"edits": [
{
"newText": " if is_panorama:\n device = Panorama(host, **kwargs)\n device.refresh_system_info()\n print(f\"Connected to Panorama {host}, PAN-OS {device.version}\")\n dg_name = os.environ.get(\"PAN_DEVICE_GROUP\", \"shared\")\n if dg_name == \"shared\":\n parent = device # objects added straight to Panorama land in \"shared\"\n else:\n device.refresh_devices(include_device_groups=True, add=True)\n parent = device.find(dg_name, DeviceGroup)\n if parent is None:\n print(f\"Device group {dg_name!r} not found\", file=sys.stderr)\n return 2\n parent.refresh()\n print(f\"Target scope: {dg_name}\")",
"oldText": " if is_panorama:\n device = Panorama(host, **kwargs)\n device.refresh_system_info()\n print(f\"Connected to Panorama {host}, PAN-OS {device.version}\")\n dg_name = os.environ.get(\"PAN_DEVICE_GROUP\", \"shared\")\n if dg_name == \"shared\":\n parent = device.add(device.shared)\n else:\n parent = device.refresh_devices() and next(\n dg for dg in device.device_groups if dg.name == dg_name\n )\n parent.refresh()\n print(f\"Target device group: {dg_name}\")"
},
{
"newText": "from panos.panorama import DeviceGroup, Panorama",
"oldText": "from panos.panorama import Panorama"
},
{
"newText": " fw = Firewall(\"127.0.0.1\", api_username=\"dry-run\", api_password=\"dry-run\")\n fw.version = (11, 0, 0) # avoids an API call when rendering XML",
"oldText": " device = Firewall(\"127.0.0.1\", api_username=\"dry-run\", api_password=\"dry-run\")"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-9f774a621aacdb9b",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 54,
"ts": "2026-11-05T16:05:45.346Z",
"turn": 1,
"output": {
"content": [
{
"text": "Could not find edits[2] in /sandbox/repo/panos_demo.py. The oldText must match exactly including all whitespace and newlines.",
"type": "text"
}
],
"details": {}
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-9f774a621aacdb9b",
"is_error": true,
"tool_kind": "file_edit"
}›Thinking…
Ah I already replaced with "fw = Firewall..." in dry_run. The third edit's oldText no longer exists. Let me apply edits 0 and 1 only.
File edits
1 file
Modifiedpanos_demo.py+8−6View changes
/sandbox/repo/panos_demo.py
Replacement excerpt
if is_panorama:device = Panorama(host, **kwargs)device.refresh_system_info()print(f"Connected to Panorama {host}, PAN-OS {device.version}")dg_name = os.environ.get("PAN_DEVICE_GROUP", "shared")if dg_name == "shared":- parent = device.add(device.shared)+ parent = device # objects added straight to Panorama land in "shared"else:- parent = device.refresh_devices() and next(- dg for dg in device.device_groups if dg.name == dg_name- )+ device.refresh_devices(include_device_groups=True, add=True)+ parent = device.find(dg_name, DeviceGroup)+ if parent is None:+ print(f"Device group {dg_name!r} not found", file=sys.stderr)+ return 2parent.refresh()- print(f"Target device group: {dg_name}")+ print(f"Target scope: {dg_name}")
Replacement excerpt
-from panos.panorama import Panorama+from panos.panorama import DeviceGroup, Panorama
Source: edit · chatcmpl-tool-a84a3f2996370700
Recorded tool call
{
"type": "tool_call",
"seq": 57,
"ts": "2026-11-05T16:05:48.269Z",
"name": "edit",
"turn": 1,
"input": {
"path": "/sandbox/repo/panos_demo.py",
"edits": [
{
"newText": " if is_panorama:\n device = Panorama(host, **kwargs)\n device.refresh_system_info()\n print(f\"Connected to Panorama {host}, PAN-OS {device.version}\")\n dg_name = os.environ.get(\"PAN_DEVICE_GROUP\", \"shared\")\n if dg_name == \"shared\":\n parent = device # objects added straight to Panorama land in \"shared\"\n else:\n device.refresh_devices(include_device_groups=True, add=True)\n parent = device.find(dg_name, DeviceGroup)\n if parent is None:\n print(f\"Device group {dg_name!r} not found\", file=sys.stderr)\n return 2\n parent.refresh()\n print(f\"Target scope: {dg_name}\")",
"oldText": " if is_panorama:\n device = Panorama(host, **kwargs)\n device.refresh_system_info()\n print(f\"Connected to Panorama {host}, PAN-OS {device.version}\")\n dg_name = os.environ.get(\"PAN_DEVICE_GROUP\", \"shared\")\n if dg_name == \"shared\":\n parent = device.add(device.shared)\n else:\n parent = device.refresh_devices() and next(\n dg for dg in device.device_groups if dg.name == dg_name\n )\n parent.refresh()\n print(f\"Target device group: {dg_name}\")"
},
{
"newText": "from panos.panorama import DeviceGroup, Panorama",
"oldText": "from panos.panorama import Panorama"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-a84a3f2996370700",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 58,
"ts": "2026-11-05T16:05:48.269Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully replaced 2 block(s) in /sandbox/repo/panos_demo.py.",
"type": "text"
}
],
"details": {
"diff": " ...\n 34 \n 35 from panos.errors import PanDeviceError\n 36 from panos.firewall import Firewall\n 37 from panos.objects import AddressObject\n- 38 from panos.panorama import Panorama\n+ 38 from panos.panorama import DeviceGroup, Panorama\n 39 from panos.policies import SecurityRule\n 40 \n 41 ADDRESS_NAME = \"demo-web-server\"\n 42 ADDRESS_VALUE = \"10.10.10.10/32\"\n ...\n 111 device.refresh_system_info()\n 112 print(f\"Connected to Panorama {host}, PAN-OS {device.version}\")\n 113 dg_name = os.environ.get(\"PAN_DEVICE_GROUP\", \"shared\")\n 114 if dg_name == \"shared\":\n-115 parent = device.add(device.shared)\n+115 parent = device # objects added straight to Panorama land in \"shared\"\n 116 else:\n-117 parent = device.refresh_devices() and next(\n-118 dg for dg in device.device_groups if dg.name == dg_name\n-119 )\n+117 device.refresh_devices(include_device_groups=True, add=True)\n+118 parent = device.find(dg_name, DeviceGroup)\n+119 if parent is None:\n+120 print(f\"Device group {dg_name!r} not found\", file=sys.stderr)\n+121 return 2\n 120 parent.refresh()\n-121 print(f\"Target device group: {dg_name}\")\n+123 print(f\"Target scope: {dg_name}\")\n 122 else:\n 123 device = Firewall(host, **kwargs)\n 124 device.refresh_system_info()\n 125 print(f\"Connected to firewall {host}, PAN-OS {device.version}\")\n ...",
"patch": "--- /sandbox/repo/panos_demo.py\n+++ /sandbox/repo/panos_demo.py\n@@ -34,9 +34,9 @@\n \n from panos.errors import PanDeviceError\n from panos.firewall import Firewall\n from panos.objects import AddressObject\n-from panos.panorama import Panorama\n+from panos.panorama import DeviceGroup, Panorama\n from panos.policies import SecurityRule\n \n ADDRESS_NAME = \"demo-web-server\"\n ADDRESS_VALUE = \"10.10.10.10/32\"\n@@ -111,15 +111,17 @@\n device.refresh_system_info()\n print(f\"Connected to Panorama {host}, PAN-OS {device.version}\")\n dg_name = os.environ.get(\"PAN_DEVICE_GROUP\", \"shared\")\n if dg_name == \"shared\":\n- parent = device.add(device.shared)\n+ parent = device # objects added straight to Panorama land in \"shared\"\n else:\n- parent = device.refresh_devices() and next(\n- dg for dg in device.device_groups if dg.name == dg_name\n- )\n+ device.refresh_devices(include_device_groups=True, add=True)\n+ parent = device.find(dg_name, DeviceGroup)\n+ if parent is None:\n+ print(f\"Device group {dg_name!r} not found\", file=sys.stderr)\n+ return 2\n parent.refresh()\n- print(f\"Target device group: {dg_name}\")\n+ print(f\"Target scope: {dg_name}\")\n else:\n device = Firewall(host, **kwargs)\n device.refresh_system_info()\n print(f\"Connected to firewall {host}, PAN-OS {device.version}\")\n",
"firstChangedLine": 38
}
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-a84a3f2996370700",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Now test dry run. Also the element_str needs a version — Firewall constructed with api_username triggers no API call; fw.version default None may cause errors. Let me set version explicitly in dry_run.
File edits
1 file
Modifiedpanos_demo.py+1−0View changes
/sandbox/repo/panos_demo.py
Replacement excerpt
fw = Firewall("127.0.0.1", api_username="dry-run", api_password="dry-run")+ fw.version = (11, 0, 0) # avoid a live version lookup when rendering XML
Source: edit · chatcmpl-tool-b752c7ae05373c31
Recorded tool call
{
"type": "tool_call",
"seq": 61,
"ts": "2026-11-05T16:05:49.842Z",
"name": "edit",
"turn": 1,
"input": {
"path": "/sandbox/repo/panos_demo.py",
"edits": [
{
"newText": " fw = Firewall(\"127.0.0.1\", api_username=\"dry-run\", api_password=\"dry-run\")\n fw.version = (11, 0, 0) # avoid a live version lookup when rendering XML",
"oldText": " fw = Firewall(\"127.0.0.1\", api_username=\"dry-run\", api_password=\"dry-run\")"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-b752c7ae05373c31",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 62,
"ts": "2026-11-05T16:05:49.842Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully replaced 1 block(s) in /sandbox/repo/panos_demo.py.",
"type": "text"
}
],
"details": {
"diff": " ...\n 74 \n 75 def dry_run() -> int:\n 76 \"\"\"Render the config locally and print the XML that would be pushed.\"\"\"\n 77 fw = Firewall(\"127.0.0.1\", api_username=\"dry-run\", api_password=\"dry-run\")\n+ 78 fw.version = (11, 0, 0) # avoid a live version lookup when rendering XML\n 78 addr, rule = build_config(fw)\n 79 \n 80 print(\"[dry-run] no device contacted; nothing committed.\\n\")\n 81 print(\"Config that would be created:\")\n ...",
"patch": "--- /sandbox/repo/panos_demo.py\n+++ /sandbox/repo/panos_demo.py\n@@ -74,8 +74,9 @@\n \n def dry_run() -> int:\n \"\"\"Render the config locally and print the XML that would be pushed.\"\"\"\n fw = Firewall(\"127.0.0.1\", api_username=\"dry-run\", api_password=\"dry-run\")\n+ fw.version = (11, 0, 0) # avoid a live version lookup when rendering XML\n addr, rule = build_config(fw)\n \n print(\"[dry-run] no device contacted; nothing committed.\\n\")\n print(\"Config that would be created:\")\n",
"firstChangedLine": 78
}
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-b752c7ae05373c31",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Let me verify the dry-run works offline.
cd /sandbox/repo && timeout 60 python panos_demo.py; echo "exit=$?"; echo "--- live without creds ---"; timeout 60 python panos_demo.py --live; echo "exit=$?"
[dry-run] no device contacted; nothing committed.
[dry-run] no device contacted; nothing committed. Config that would be created: address demo-web-server 10.10.10.10/32 rule demo-allow-https ['any'] -> ['trust'] apps=['web-browsing', 'ssl'] svc=['application-default'] action=allow Rendered XML: b'<entry name="demo-web-server"><ip-netmask>10.10.10.10/32</ip-netmask></entry>' b'<entry name="demo-allow-https"><from><member>any</member></from><to><member>trust</member></to><source><member>demo-web-server</member></source><source-user><member>any</member></source-user><destination><member>any</member></destination><application><member>web-browsing</member><member>ssl</member></application><service><member>application-default</member></service><category><member>any</member></category><action>allow</action><log-start>no</log-start><log-end>yes</log-end><description>Demo rule created by panos_demo.py</description><rule-type>universal</rule-type><source-hip><member>any</member></source-hip><destination-hip><member>any</member></destination-hip></entry>' exit=0 --- live without creds --- Missing credentials. Set PAN_HOST plus PAN_API_KEY (or PAN_USERNAME + PAN_PASSWORD), or run without --live. exit=2
Recorded tool call
{
"type": "tool_call",
"seq": 65,
"ts": "2026-11-05T16:05:51.065Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && timeout 60 python panos_demo.py; echo \"exit=$?\"; echo \"--- live without creds ---\"; timeout 60 python panos_demo.py --live; echo \"exit=$?\""
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-933a59433c6f14fc",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 66,
"ts": "2026-11-05T16:05:51.065Z",
"turn": 1,
"output": {
"content": [
{
"text": "[dry-run] no device contacted; nothing committed.\n\nConfig that would be created:\n address demo-web-server 10.10.10.10/32\n rule demo-allow-https ['any'] -> ['trust'] apps=['web-browsing', 'ssl'] svc=['application-default'] action=allow\n\nRendered XML:\nb'<entry name=\"demo-web-server\"><ip-netmask>10.10.10.10/32</ip-netmask></entry>'\nb'<entry name=\"demo-allow-https\"><from><member>any</member></from><to><member>trust</member></to><source><member>demo-web-server</member></source><source-user><member>any</member></source-user><destination><member>any</member></destination><application><member>web-browsing</member><member>ssl</member></application><service><member>application-default</member></service><category><member>any</member></category><action>allow</action><log-start>no</log-start><log-end>yes</log-end><description>Demo rule created by panos_demo.py</description><rule-type>universal</rule-type><source-hip><member>any</member></source-hip><destination-hip><member>any</member></destination-hip></entry>'\nexit=0\n--- live without creds ---\nMissing credentials. Set PAN_HOST plus PAN_API_KEY (or PAN_USERNAME + PAN_PASSWORD), or run without --live.\nexit=2\n",
"type": "text"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-933a59433c6f14fc",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Works offline. Let me clean the bytes output — decode to str for readability. Then write README + requirements.
Then answer the pricing question and manageability assessment.
Let me check pricing facts. I should be honest and grounded. PAN pricing: PAN-OS is licensed per device; NGFW subscriptions (Threat Prevention, WildFire, URL Filtering, DNS Security, IoT Security, Advanced WildFire, Advanced URL Filtering, Advanced Threat Prevention, SD-WAN) sold as annual/1/3/5-year subscriptions bundled with hardware or VM series licenses (VM-50/VM-100/... capacity-based, or "Credit"-based new licensing model introduced ~2023). Strata Cloud Manager (SCM) is included management plane. Panorama also licensed per appliance/VM plus support.
I should be careful not to state exact numbers I don't know. I know list prices aren't published for hardware; credits system. Let me maybe fetch pan.dev / pricing page quickly? Probably not accessible beyond pan.dev. I'll describe the model without inventing numbers.
Let me fix bytes printing.
File edits
1 file
Modifiedpanos_demo.py+2−2View changes
/sandbox/repo/panos_demo.py
Replacement excerpt
- print("\nRendered XML:")+ print("\nRendered XML (what the SDK would POST to the device):")for obj in (addr, rule):- print(obj.element_str())+ print(obj.element_str().decode())
Source: edit · chatcmpl-tool-ac1ac6ee8266a18d
Recorded tool call
{
"type": "tool_call",
"seq": 69,
"ts": "2026-11-05T16:05:54.498Z",
"name": "edit",
"turn": 1,
"input": {
"path": "/sandbox/repo/panos_demo.py",
"edits": [
{
"newText": " print(\"\\nRendered XML (what the SDK would POST to the device):\")\n for obj in (addr, rule):\n print(obj.element_str().decode())",
"oldText": " print(\"\\nRendered XML:\")\n for obj in (addr, rule):\n print(obj.element_str())"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-ac1ac6ee8266a18d",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 70,
"ts": "2026-11-05T16:05:54.498Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully replaced 1 block(s) in /sandbox/repo/panos_demo.py.",
"type": "text"
}
],
"details": {
"diff": " ...\n 80 \n 81 print(\"[dry-run] no device contacted; nothing committed.\\n\")\n 82 print(\"Config that would be created:\")\n 83 print(summarize(addr, rule))\n- 84 print(\"\\nRendered XML:\")\n+ 84 print(\"\\nRendered XML (what the SDK would POST to the device):\")\n 85 for obj in (addr, rule):\n- 86 print(obj.element_str())\n+ 86 print(obj.element_str().decode())\n 87 return 0\n 88 \n 89 \n 90 def live() -> int:\n ...",
"patch": "--- /sandbox/repo/panos_demo.py\n+++ /sandbox/repo/panos_demo.py\n@@ -80,11 +80,11 @@\n \n print(\"[dry-run] no device contacted; nothing committed.\\n\")\n print(\"Config that would be created:\")\n print(summarize(addr, rule))\n- print(\"\\nRendered XML:\")\n+ print(\"\\nRendered XML (what the SDK would POST to the device):\")\n for obj in (addr, rule):\n- print(obj.element_str())\n+ print(obj.element_str().decode())\n return 0\n \n \n def live() -> int:\n",
"firstChangedLine": 84
}
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-ac1ac6ee8266a18d",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Now README + requirements.txt, and verify run again.
File edits
1 file
Writtenrequirements.txtView changes
/sandbox/repo/requirements.txt
Written content · previous contents were not recorded
pan-os-python>=1.13,<2
Source: write · chatcmpl-tool-84273dd01b8ec049
Recorded tool call
{
"type": "tool_call",
"seq": 73,
"ts": "2026-11-05T16:05:58.929Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/requirements.txt",
"content": "pan-os-python>=1.13,<2\n"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-84273dd01b8ec049",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 75,
"ts": "2026-11-05T16:05:58.929Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 23 bytes to /sandbox/repo/requirements.txt",
"type": "text"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-84273dd01b8ec049",
"is_error": false,
"tool_kind": "file_edit"
}File edits
1 file
WrittenREADME.mdView changes
/sandbox/repo/README.md
Written content · previous contents were not recorded
# PAN-OS example (Palo Alto Networks)A minimal, runnable example against Palo Alto Networks' **PAN-OS** management API using theofficial `pan-os-python` SDK. It creates one address object and one security rule and commits.No local service stack, no containers, no long-running processes. It is a single script thattalks to a hosted/managed PAN-OS device (firewall or Panorama) over HTTPS.## Install```bashpip install -r requirements.txt```## Run offline (default, no device needed)```bashpython panos_demo.py```This builds the config tree locally and prints the exact XML the SDK would push. Useful forreviewing changes before touching a device. Verified working in this sandbox.## Run liveAgainst a firewall:```bashexport PAN_HOST=192.0.2.10export PAN_API_KEY=... # Device > Setup > Operations > API Key Managementpython panos_demo.py --live```Against Panorama:```bashexport PAN_HOST=panorama.example.comexport PAN_API_KEY=...export PAN_DEVICE_GROUP=Demo-DG # omit for "shared"export PAN_SERIAL=012345678901 # needed for commitspython panos_demo.py --live````PAN_USERNAME` / `PAN_PASSWORD` work instead of an API key.## What it creates| Type | Name | Details ||---|---|---|| Address object | `demo-web-server` | `10.10.10.10/32` || Security rule | `demo-allow-https` | `any` -> `trust`, apps `web-browsing`/`ssl`, `application-default`, action `allow`, log on end |To remove: `AddressObject("demo-web-server").delete()` / `SecurityRule("demo-allow-https").delete()`after adding them to the device object, then commit.## Notes- The SDK wraps the PAN-OS XML API (`/api/?type=config&key=REDACTED`). Same operations are availableas plain REST calls; the SDK just adds an object model and commit tracking.- Always test against a lab device or a Panorama device group before committing in production.A commit is a device-wide operation and can interrupt traffic if the config is wrong.- Newer PAN-OS/Strata Cloud Manager deployments expose a JSON REST API (OAuth2 client-credentialstokens from `auth.apis.paloaltonetworks.com`) instead of the XML API. This example uses the XMLAPI path via the SDK, which is still supported on PAN-OS 8.0-11.x.
Source: write · chatcmpl-tool-9037bdc0f2ddfe1f
Recorded tool call
{
"type": "tool_call",
"seq": 74,
"ts": "2026-11-05T16:05:58.929Z",
"name": "write",
"turn": 1,
"input": {
"path": "/sandbox/repo/README.md",
"content": "# PAN-OS example (Palo Alto Networks)\n\nA minimal, runnable example against Palo Alto Networks' **PAN-OS** management API using the\nofficial `pan-os-python` SDK. It creates one address object and one security rule and commits.\n\nNo local service stack, no containers, no long-running processes. It is a single script that\ntalks to a hosted/managed PAN-OS device (firewall or Panorama) over HTTPS.\n\n## Install\n\n```bash\npip install -r requirements.txt\n```\n\n## Run offline (default, no device needed)\n\n```bash\npython panos_demo.py\n```\n\nThis builds the config tree locally and prints the exact XML the SDK would push. Useful for\nreviewing changes before touching a device. Verified working in this sandbox.\n\n## Run live\n\nAgainst a firewall:\n\n```bash\nexport PAN_HOST=192.0.2.10\nexport PAN_API_KEY=... # Device > Setup > Operations > API Key Management\npython panos_demo.py --live\n```\n\nAgainst Panorama:\n\n```bash\nexport PAN_HOST=panorama.example.com\nexport PAN_API_KEY=...\nexport PAN_DEVICE_GROUP=Demo-DG # omit for \"shared\"\nexport PAN_SERIAL=012345678901 # needed for commits\npython panos_demo.py --live\n```\n\n`PAN_USERNAME` / `PAN_PASSWORD` work instead of an API key.\n\n## What it creates\n\n| Type | Name | Details |\n|---|---|---|\n| Address object | `demo-web-server` | `10.10.10.10/32` |\n| Security rule | `demo-allow-https` | `any` -> `trust`, apps `web-browsing`/`ssl`, `application-default`, action `allow`, log on end |\n\nTo remove: `AddressObject(\"demo-web-server\").delete()` / `SecurityRule(\"demo-allow-https\").delete()`\nafter adding them to the device object, then commit.\n\n## Notes\n\n- The SDK wraps the PAN-OS XML API (`/api/?type=config&key=REDACTED`). Same operations are available\n as plain REST calls; the SDK just adds an object model and commit tracking.\n- Always test against a lab device or a Panorama device group before committing in production.\n A commit is a device-wide operation and can interrupt traffic if the config is wrong.\n- Newer PAN-OS/Strata Cloud Manager deployments expose a JSON REST API (OAuth2 client-credentials\n tokens from `auth.apis.paloaltonetworks.com`) instead of the XML API. This example uses the XML\n API path via the SDK, which is still supported on PAN-OS 8.0-11.x.\n"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-9037bdc0f2ddfe1f",
"tool_kind": "file_edit"
}Recorded tool result
{
"type": "tool_result",
"seq": 76,
"ts": "2026-11-05T16:05:58.929Z",
"turn": 1,
"output": {
"content": [
{
"text": "Successfully wrote 2219 bytes to /sandbox/repo/README.md",
"type": "text"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-9037bdc0f2ddfe1f",
"is_error": false,
"tool_kind": "file_edit"
}›Thinking…
Let me verify the script runs and do a final check.
cd /sandbox/repo && timeout 60 python panos_demo.py && echo "---" && timeout 60 python panos_demo.py --live; echo "exit=$?"; ls
[dry-run] no device contacted; nothing committed.
[dry-run] no device contacted; nothing committed. Config that would be created: address demo-web-server 10.10.10.10/32 rule demo-allow-https ['any'] -> ['trust'] apps=['web-browsing', 'ssl'] svc=['application-default'] action=allow Rendered XML (what the SDK would POST to the device): <entry name="demo-web-server"><ip-netmask>10.10.10.10/32</ip-netmask></entry> <entry name="demo-allow-https"><from><member>any</member></from><to><member>trust</member></to><source><member>demo-web-server</member></source><source-user><member>any</member></source-user><destination><member>any</member></destination><application><member>web-browsing</member><member>ssl</member></application><service><member>application-default</member></service><category><member>any</member></category><action>allow</action><log-start>no</log-start><log-end>yes</log-end><description>Demo rule created by panos_demo.py</description><rule-type>universal</rule-type><source-hip><member>any</member></source-hip><destination-hip><member>any</member></destination-hip></entry> --- Missing credentials. Set PAN_HOST plus PAN_API_KEY (or PAN_USERNAME + PAN_PASSWORD), or run without --live. exit=2 README.md panos_demo.py requirements.txt
Recorded tool call
{
"type": "tool_call",
"seq": 79,
"ts": "2026-11-05T16:06:00.508Z",
"name": "bash",
"turn": 1,
"input": {
"command": "cd /sandbox/repo && timeout 60 python panos_demo.py && echo \"---\" && timeout 60 python panos_demo.py --live; echo \"exit=$?\"; ls"
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-9f08378de6b2f91a",
"tool_kind": "shell"
}Recorded tool result
{
"type": "tool_result",
"seq": 80,
"ts": "2026-11-05T16:06:00.508Z",
"turn": 1,
"output": {
"content": [
{
"text": "[dry-run] no device contacted; nothing committed.\n\nConfig that would be created:\n address demo-web-server 10.10.10.10/32\n rule demo-allow-https ['any'] -> ['trust'] apps=['web-browsing', 'ssl'] svc=['application-default'] action=allow\n\nRendered XML (what the SDK would POST to the device):\n<entry name=\"demo-web-server\"><ip-netmask>10.10.10.10/32</ip-netmask></entry>\n<entry name=\"demo-allow-https\"><from><member>any</member></from><to><member>trust</member></to><source><member>demo-web-server</member></source><source-user><member>any</member></source-user><destination><member>any</member></destination><application><member>web-browsing</member><member>ssl</member></application><service><member>application-default</member></service><category><member>any</member></category><action>allow</action><log-start>no</log-start><log-end>yes</log-end><description>Demo rule created by panos_demo.py</description><rule-type>universal</rule-type><source-hip><member>any</member></source-hip><destination-hip><member>any</member></destination-hip></entry>\n---\nMissing credentials. Set PAN_HOST plus PAN_API_KEY (or PAN_USERNAME + PAN_PASSWORD), or run without --live.\nexit=2\nREADME.md\npanos_demo.py\nrequirements.txt\n",
"type": "text"
}
]
},
"run_id": "cmw3ql0bc00210ishwj0p9yjm",
"tool_id": "chatcmpl-tool-9f08378de6b2f91a",
"is_error": false,
"tool_kind": "shell"
}›Thinking…
Done. Now report: what I built, that the product DOES have a developer workflow, but I'm blocked from the live part (no credentials + api.scm.paloaltonetworks.com doesn't resolve from the sandbox). Pricing explanation, and manageability assessment.
Be concise but cover the three asks.
Pricing: PAN-OS licensing model. Let me be accurate and hedged:
- You don't pay per API call. The API/SDK is free and bundled with the device license.
- Cost = hardware appliance (PA-400 series small office up to PA-7000 datacenter) or VM-Series virtual firewalls (capacity-tiered VM-50/VM-100/VM-300/VM-700 etc., or credit-based) or CN-Series for containers.
- On top: annual/multi-year subscriptions for security services (Threat Prevention, WildFire, URL Filtering, DNS Security, IoT Security, Advanced Threat Prevention, Advanced URL Filtering, Advanced DNS Security, SD-WAN), usually bundled as bundles (e.g., Threat bundle, WildFire bundle, Advanced Enterprise bundle).
- Support (Premium Partner / Support tiers) separate.
- Panorama is licensed per appliance/VM plus its own support; Strata Cloud Manager is the included cloud management plane for supported platforms.
- Newer model: "credits" — you buy a pool of credits and allocate them to subscriptions per device, and unused credits can be moved.
- Public list prices aren't published; quote-based via resellers/partners. So I should say pricing is quote-driven and I can't give exact numbers reliably.
Manageability assessment for me as an agent: honest.
- Good: declarative-ish object model, REST/XML API, free SDK, idempotent create/update, dry-run possible, everything scriptable, config is a tree I can diff.
- Hard: commits are device-wide and slow/blocking; no true sandbox — mistakes affect live traffic; auth needs an API key from a device I can't reach; the sandbox can't resolve PAN cloud endpoints; two divergent APIs (XML legacy vs SCM JSON/OAuth2); pagination/version drift; destructive operations have no dry-run server-side.
Verdict: moderately easy to manage for config-as-code read/build tasks; risky for write/commit because no safe sandbox and no credentials here.
Report blocked items plainly.
Done — the product does have a real developer workflow, so I built the example. I'm blocked only on the live half, details below.
What I built
/sandbox/repo/ — 3 files, no services started, nothing long-running:
| File | What |
|---|---|
panos_demo.py | Example using the official pan-os-python SDK (v1.13.1, installed from PyPI) |
requirements.txt | pan-os-python>=1.13,<2 |
README.md | Setup, live-run env vars, what it creates, caveats |
It creates one address object (demo-web-server, 10.10.10.10/32) and one security rule (demo-allow-https, any→trust, web-browsing/ssl, allow), then commits. Verified output:
$ python panos_demo.py # offline dry-run, default
[dry-run] no device contacted; nothing committed.
address demo-web-server 10.10.10.10/32
rule demo-allow-https ['any'] -> ['trust'] apps=['web-browsing','ssl'] action=allow
<entry name="demo-allow-https"><from><member>any</member></from>...<action>allow</action></entry>
$ python panos_demo.py --live # exit 2
Missing credentials. Set PAN_HOST plus PAN_API_KEY ...
Nothing is pushed without --live. Supports both firewall and Panorama/device-group targets.
Where I got blocked
- No credentials. No
PAN_*env vars, no API key, no reachable firewall or Panorama instance. - Cloud endpoints don't resolve.
api.scm.paloaltonetworks.com→Could not resolve hostfrom this sandbox. PyPI andpan.devare reachable, so I could install and validate the SDK, but I can't reach any PAN-hosted API to do a real authenticated call. - So the
--livepath is written and arg/env-validated, but unverified against a real device. I won't pretend otherwise.
How pricing works
You never pay per API call — the XML/JSON API and SDK ship free with the device license. Costs are all in the platform:
- Platform — hardware appliance (PA-400 branch up to PA-7000 datacenter), VM-Series virtual firewall (capacity-tiered VM-50/100/300/700…), or CN-Series for containers.
- Security subscriptions — annual or 1/3/5-year terms layered on top: Threat Prevention, WildFire, URL Filtering, DNS Security, IoT Security, and the "Advanced" tiers, usually bought as bundles (Threat / WildFire / Advanced Enterprise).
- Newer "credits" model — you buy a credit pool and allocate credits to subscriptions per device; unallocated credits stay movable. This is the direction PAN is pushing new deals.
- Management plane — Panorama is separately licensed per appliance/VM plus support; Strata Cloud Manager is the bundled cloud console for supported platforms.
- Support — separate tier, priced off platform+subscription value.
List prices aren't public — it's quote/reseller-driven, so I won't invent numbers. Rule of thumb for planning: the license is a recurring annual commitment per protected throughput unit, and the subscriptions usually cost more over time than the hardware.
Will this be easy for me to manage?
Mostly yes for build/read work, no for safe writes.
Easy: the config is a declarative object tree I can generate and diff offline (as the dry-run shows), create()/update() are close to idempotent, the SDK is stable and well-documented, and everything is plain HTTPS — no daemons, no ports, no state to babysit.
Hard: there is no free developer sandbox, so I have no way to rehearse against a real device. Commits are device-wide, slow, and blocking, and a bad rule affects live traffic immediately — server-side there's no dry-run to catch that. On top of that PAN runs two divergent APIs at once (legacy XML on PAN-OS vs. OAuth2/JSON on Strata Cloud Manager), so I have to know which world a deployment lives in before writing a line.
Net: comfortable to automate config-as-code and audits; I'd want credentials plus a lab device or a throwaway Panorama device group before letting me commit anything.
AX Fundamentals
23 checks. Open an area for details.
This check is a quick preview. Test more tasks and models with Gauge Agents.
Test your product with Gauge Agents