Request Basics
Send every pfSense Actions request to its HTTPS address with a personal access token, a JSON body where the route needs one, and read the status code and JSON reply as the result.
Base URL
Use the HTTPS origin of your reverse proxy, such as https://actions.example.net. The routes live under /api/v1, and serving them under a sub-path isn't supported.
| Route | Also at | Scope |
|---|---|---|
GET /api/v1/connectivity | GET / | pfsense:read |
GET /api/v1/system | GET /system | pfsense:read |
POST /api/v1/ping | POST /ping | reachability:read |
POST /api/v1/wol | POST /wol | wol:execute |
The shorter paths run the same handlers, so scripts written for 1.x keep working with GET /, POST /ping, and POST /wol.
Authentication
Send the token in the Authorization header of every request.
GET /api/v1/system HTTP/1.1
Host: actions.example.net
Authorization: Bearer pfa_...
- The
Bearerscheme name is matched without regard to case, and a token ispfa_followed by 43 URL-safe base64 characters. - A missing, malformed, unknown, expired, or revoked token returns
401with the codeauthentication_required, with no detail about which check failed. - A valid token without the route's scope returns
403with the codescope_required. - A console sign-in can't run an action, so only tokens work here.
- The console's own routes work the other way around. They need a signed-in browser session, so a request with a token gets
403with the codesession_required, as described in Notifications.
Scopes
| Scope | Routes | Changes state |
|---|---|---|
pfsense:read | GET /api/v1/connectivity, GET /api/v1/system | No |
reachability:read | POST /api/v1/ping | No |
wol:execute | POST /api/v1/wol | Yes |
Scopes are exact and never grant each other's routes. Token changes made in the operations console apply to the next request, with no restart.
Request Bodies
POST /api/v1/pingandPOST /api/v1/woltake a JSON body sent withContent-Type: application/json. Without that header, the body isn't read, so the route answers400.- Malformed JSON returns
400with the codeinvalid_json, and a body over 64 KB returns413, both before the token is checked. - Field types are strict, so
"count": "3"is rejected where"count": 3is accepted. Fields that a route doesn't define are ignored.
Responses
Every route answers with JSON, and each one is a data-minimizing response that leaves out SSH output, error details, and credentials.
{
"connected": true
}
{
"error": {
"code": "pfsense_unavailable",
"message": "pfSense Actions couldn't sign in to pfSense over SSH."
}
}
GET /api/v1/systemreturns three system fields, and the other routes return one field set totrue.- A failed request returns an
errorobject with a stablecodefor scripts and amessagefor people. - A body that fails validation also lists each problem in
issues, with thefieldname, amessage, and thepathas a list, and the top-levelmessagerepeats the first one.
{
"error": {
"code": "invalid_request",
"message": "Enter a MAC address such as 00:11:22:33:44:55, in all lowercase or all uppercase.",
"issues": [
{
"field": "macAddress",
"message": "Enter a MAC address such as 00:11:22:33:44:55, in all lowercase or all uppercase.",
"path": ["macAddress"]
}
]
}
}
Keep Tokens out of Process Arguments
Passing the token with curl's -H option puts it in the process arguments, where other users on the machine can see it. Piping the header to curl's --config - keeps it out, as long as printf is the shell's builtin rather than /usr/bin/printf.
read -r -s PFSENSE_ACTIONS_TOKEN
printf 'header = "Authorization: Bearer %s"\n' "$PFSENSE_ACTIONS_TOKEN" \
| curl --config - --silent --show-error --fail-with-body \
https://actions.example.net/api/v1/system
unset PFSENSE_ACTIONS_TOKEN
read -r -sreads the token without showing it or saving it to the shell history.- The other examples in these docs use the same pattern.