Hi,
I'm trying to add a Client Specific Override (CSO) using the OPNsense API and curl, but I keep getting the response {"result":"failed"}.
I've tried various payload formats, using this (https://docs.opnsense.org/development/api/core/openvpn.html) OPNsense API doc as a base. Since I couldn't find a full schema for OPNsense CSO, I borrowed the format from the pf API here: https://pfrest.org/api-docs/#/VPN/postVPNOpenVPNCSOEndpoint
Here's one example I tested:
curl -v -k --location https://my.opnsense.host/api/openvpn/client_overwrites/add \
-u "key:secret" \
--header 'Content-Type: application/json' \
--data '{
"common_name": "test.user",
"disable": false,
"block": false,
"description": "IP-Reservation",
"server_list": ["OVPN-Proton-IN"],
"tunnel_network": "172.17.2.107/24"
}'
I also tried with escaping quotes and with other field combinations, but always got the same result.
The API call completes successfully with HTTP 200, but the body returns: {"result":"failed"}
full output:
* Host my.opnsense.host:443 was resolved.
* IPv6: (none)
* IPv4: 192.0.2.123
* Trying 192.0.2.123:443...
* Connected to your.opnsense.host (192.0.2.123) port 443
* ALPN: curl offers h2,http/1.1
* (TLS handshake and certificate ok)
* using HTTP/2
* Server auth using Basic with user '[REDACTED]'
> POST /api/openvpn/client_overwrites/add HTTP/2
> Host: your.opnsense.host
> Authorization: Basic [REDACTED]
> User-Agent: curl/8.7.1
> Accept: */*
> Content-Type: application/json
> Content-Length: 213
>
* upload completely sent off: 213 bytes
< HTTP/2 200
< content-type: application/json; charset=UTF-8
< server: OPNsense
< date: Fri, 11 Apr 2025 17:06:49 GMT
< set-cookie: PHPSESSID=[REDACTED]; path=/; secure; HttpOnly; SameSite=Lax
<
{"result":"failed"}
Any idea what I might be doing wrong? Is the tunnel_network key invalid in OPNsense? What's the correct schema for the CSO add endpoint?
Thanks!
Maybe this new guide can help:
https://github.com/opnsense/docs/pull/694/files
Hi again, and thanks for quick reply!
After several tests and with help from search and instances/search API calls, I believe I've finally figured out why my CSO API calls keep returning {"result":"failed"}.
Root cause: missing name field in OpenVPN instances?
The API endpoint client_overwrites/add expects server_list to include internal OpenVPN instance names, not just their descriptions. These names are normally set internally when creating an instance, but in my case, all instances returned:
"name": null,
"description": "OVPN-Proton-IN"
This explains why the CSO can't be added – the instance has no valid internal name, and the API doesn't know where to attach the override.
curl -X POST 'https://my.opnsense/api/openvpn/instances/search' \
-u 'APIKEY:APISECRET' \
-H 'accept: application/json' \
-H 'content-type: application/json' \
--data-raw '{}' \
--insecure | jq '.rows[] | {name, description}'
Am I wrong?
Follow-up: Still getting {"result":"failed"} despite updated syntax and values
I tried adjusting the request syntax after reviewing the updated API docs and checking existing CSO entries via the search endpoint.
Here's the command I used:
curl -X POST 'https://my.opnsense.host/api/openvpn/client_overwrites/add' \
-u 'APIKEY:APISECRET' \
-H 'accept: application/json' \
-H 'content-type: application/json' \
--data-raw '{
"common_name": "test.user",
"description": "IP-Reservation",
"servers": "OVPN-Proton-IN (52002 / UDP)",
"tunnel_network": "172.17.2.107/32"
}' \
--insecure
Before sending the request, I verified the servers value by listing current CSOs with:
curl -X POST 'https://my.opnsense.host/api/openvpn/client_overwrites/search' \
-u 'APIKEY:APISECRET' \
-H 'accept: application/json' \
-H 'content-type: application/json' \
--data-raw '{}' \
--insecure | jq '.rows[] | select(.servers | startswith("OVPN"))'
Which returned:
{
"uuid": "298345e2-da80-45a4-ad65-295670b148d1",
{
"uuid": "298345e2-da80-45a4-ad65-295670b148d1",
"enabled": "1",
"servers": "OVPN-Proton-IN (52002 / UDP)",
"common_name": "howno",
"block": "0",
"push_reset": "0",
"tunnel_network": "",
"tunnel_networkv6": "",
"local_networks": "",
"remote_networks": "",
"route_gateway": "",
"redirect_gateway": "",
"register_dns": "0",
"dns_domain": "",
"dns_domain_search": "",
"dns_servers": "",
"ntp_servers": "",
"wins_servers": "",
"description": ""
}
}
I updated the request payload accordingly, but unfortunately, the API still responds with
{"result":"failed"}
Update: Got it working – here's where I went wrong and what the fix looks like, thanks again for the guide!
I was doing Incorrect JSON structure – The API expects a top-level key called "cso", like this:
{
"cso": {
"common_name": "...",
...
}
}
And wrong servers field – You must provide the UUID of the OpenVPN instance (not its name or description).
You can retrieve these UUIDs by calling:
curl -X POST 'https://your.opnsense/api/openvpn/instances/search' ...
Have another Q, what about removing CSO. By inspecting API while removing CSO i can not see the correct endpoint. Im trying it with:
curl -X POST 'https://my.opnsense.host/api/openvpn/client_overwrites/del' \
-u 'APIKEY:APISECRET' \
-H 'accept: application/json' \
-H 'content-type: application/json' \
--data-raw '{"uuid": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"}' \
--insecure
and getting
{"errorMessage":"Endpoint not found"}
ok, at Network/Headers was the answer for correct endpoint.
{OPNSENSE_URL}/api/openvpn/client_overwrites/del/{uuid}
thanks again!