# 2026-07-13 -- D-113(a2) PROVEN: the OPNsense REST API round-trips the edge config

The API path adopted in D-113 (option a2) is now demonstrated working against the LIVE Office1
edge. This closes the open question that gated the ruling.

## Bootstrap (the one manual step, by design)

An API key was minted for `root` in the GUI. In OPNsense **26.1** this is NOT in the user-edit
dialog and NOT in the ApiKeys tab (that tab only wires `search_api_key` + `del_api_key` -- it
can list and delete, but CANNOT create). It is a **row action on the Users list**: the
`fa-ticket` icon, "Create and download API key for this user" (source:
`mvc/app/views/OPNsense/Auth/user.volt`). Earlier instructions in this repo referencing
"edit user -> API keys -> +" describe the OLD UI and are wrong for 26.1.

`ApiKeyField::add()` mints `key` and `secret` as `base64(random_bytes(60))` (80 chars each) and
persists `key|crypt(secret,'$6$')` -- **the secret is shown exactly once and is stored hashed**.
Lose it and you delete the key and mint a new one.

Key stored at `~/vr1-office1-creds/opnsense-api.txt` (0600, operator-only, never read into agent
context). SEC-007 amended to cover it.

## Measured, against the live edge

    GET  core/firmware/status      -> HTTP 200, OPNsense 26.1 (auth works)
    GET  kea/dhcpv4/get            -> dhcpv4.general.enabled = 1, interfaces = lan
    POST kea/dhcpv4/search_subnet  -> 1 row:

        subnet                          = 10.10.0.0/24
        pools                           = 10.10.0.100-10.10.0.199
        option_data.routers             = 10.10.0.1
        option_data.domain_name_servers = 10.10.0.1
        option_data.domain_name         = office1.vr1.cloud.neumatrix.local
        description                     = LAN DHCP

The API reads back EXACTLY the config we pushed via config.xml yesterday -- and exposes it as
TYPED, NAMED FIELDS (`option_data.routers`, `pools`, ...), not free-form XML. That is the whole
(a2) argument, concretely: DOCFIX-191 (missing sshd), DOCFIX-192 (silenced console) and
DOCFIX-193 (an inert ISC `<dhcpd>` block against a Kea backend) are not expressible here. You
cannot omit a typed field that has a default, and you cannot address the wrong DHCP backend when
the endpoint IS the backend.

## Endpoint map (measured from Dhcpv4Controller.php, not guessed)

    get | searchSubnet | getSubnet | addSubnet | setSubnet | delSubnet
    searchReservation | getReservation | addReservation | setReservation | delReservation
    downloadReservations | uploadReservations | searchPeer | ... | delPeer

Note OPNsense maps camelCase actions to snake_case URLs: `searchSubnetAction` ->
`POST /api/kea/dhcpv4/search_subnet`. Response root key is `dhcpv4` (NOT `dhcp4` -- the config
XML element name differs from the API key name; do not infer one from the other).

## NOT done yet

- **No WRITE has been performed through the API.** Read is proven; the write path
  (`setSubnet` + a reconfigure) is a live mutation and remains gated.
- The `config.xml` template is still in place. It should now be reduced to a MINIMAL bootstrap
  (sshd + root key + console + nothing else), with DHCP/firewall/interfaces moving to the API.
  Until that lands, DO NOT push a full rendered config.xml -- it would clobber API-managed state.

## Revert

Nothing to revert: this change is read-only against the edge plus documentation. To revoke the
credential: OPNsense GUI -> System > Access > Users > root row > ApiKeys tab -> delete, then
`rm ~/vr1-office1-creds/opnsense-api.txt`.
