Public API
A REST API over everything in the organization: devices, groups, access rules, networks, DNS, enrollment keys, members and Activity, authenticated with an access token.
The dashboard is built on a REST API, and your own tools can use the same API. What the dashboard does to devices, groups, access rules, networks, DNS, enrollment keys and members, a script can do over HTTPS. It can keep access rules in version control, fill groups from an inventory, copy Activity into a log system, or check in a test what a device can reach.
The base URL is https://app.sxaccess.com, and every path starts with /api. Requests and responses are JSON.
Authentication
Every request carries an access token in the Authorization header, after the word Token: Authorization: Token sxp_.... Tokens always start with sxp_, so secret scanners can recognise one that leaks into a repository or a log.
Issue tokens to a service account rather than to a person. The automation then keeps working when people leave, and Activity names the service account as the one who made each change. Members whose role is owner, admin or network admin can also create a token for themselves, under Profile Settings → Tokens in the account menu; that token stops working when the member is blocked or removed. A token acts with the role of its account, in the organization it was issued in - see Roles.
The dashboard calls the same API with the sign-in session of the person using it; scripts and tools use an access token. Paths under /api/auth/ serve the sign-in pages and take no token.
Get a token
- 1
Create a service account
Under People → Service accounts, select Create service account. Name it after the system that will use it, such as
ci-provisioner, set Role to the narrowest role that does the job, then select Create service account. Role starts at User, which can read but not change anything: a script that changes access rules needs Network Admin or Admin. - 2
Create a token
Open the service account, go to Tokens and select Create token. Enter a Name and set Expires in: 1 to 365 days, 30 by default. Only owners and admins can create tokens for a service account.
- 3
Copy it now
The token is shown once, right after it is created. SXAccess keeps only a hash of it and cannot show it again, so put it straight into your secret manager or your CI system's secret store. Before it expires, create the next token, move your tool over, then delete the old one.
Organizations
Every call acts in one organization. An access token always acts in the organization it was issued in, so a tool that manages two organizations needs a token from each. GET /api/organizations returns the organization a call acts in, as a list with one entry.
A person who is a member of several organizations and calls the API with their sign-in session picks one with the X-Organization-Id header, set to the organization's ID; without it, the call acts in their default organization. Access tokens do not need the header, and it does not move a token to another organization. Organizations covers creating and switching organizations.
Roles
A token can do what the role of its account allows in that organization, and nothing more. The role is read again on every request, so a change of role applies from the next call. Service accounts may read everything in their organization whatever their role; the role decides what they may change. The full matrix is in Roles and permissions.
| Role | What a token with this role can do |
|---|---|
owner | Everything, including deleting the organization. Service accounts cannot hold this role. |
admin | Everything else: devices, members and service accounts, enrollment keys, groups, access rules, device requirements, networks, routes, DNS and the organization's settings. |
network_admin | Create, change and delete groups, access rules, device requirements, networks with their resources and routing devices, routes and DNS. Read devices, members, enrollment keys, Activity and settings without changing them. |
auditor | Read everything, change nothing. |
user | No management rights. This is the role for people who only connect their own devices. |
Endpoints
Paths are written as the API names them. A collection answers GET with a list and POST with a new object, and an object's own path, such as /api/groups/{groupId}, answers GET, PUT and DELETE. The table notes the exceptions.
| Path | What it holds |
|---|---|
/api/devices | Devices. ?name= and ?ip= narrow the list to devices whose name or overlay address contains the value. PUT renames a device and switches its SSH server and session expiry. There is no POST: devices join by enrolling. |
/api/devices/{deviceId}/reachable-devices | The devices that device can connect to under the current access rules. GET only; see the example. |
/api/enrollment-keys | Enrollment keys. The key itself is in the answer to POST only, and expires_in is in seconds. Afterwards only auto_groups and revoked can change. |
/api/access-rules | Access rules. Each holds its rule in rules, an array with one entry: sources, destinations (group IDs) or a destinationResource, protocol, ports, bidirectional and action. source_device_requirements applies device requirements to the sources. |
/api/device-requirements | Device requirements and their checks: sx_version_check, os_version_check, geo_location_check, device_network_range_check and process_check. |
/api/groups | Groups, with their devices and resources. The group All holds every device and cannot be changed or deleted. |
/api/networks | Networks. Beneath each one, /{networkId}/resources holds the hosts, subnets and domains it makes reachable, and /{networkId}/connectors holds its routing devices: a device or device groups, each with a metric and masquerade. /api/networks/resources and /api/networks/connectors list them across all networks (GET only). |
/api/network-routes | Routes: an address range or a list of domains, sent through a routing device or a device group. An Internet gateway is a route to 0.0.0.0/0. |
/api/dns/servers | DNS servers. Each entry holds up to three server addresses, the groups whose devices use them and the match domains they answer for. |
/api/dns/config | DNS settings: the groups left out of DNS management. GET and PUT. |
/api/activity | Activity: the organization's most recent 10,000 events, newest first, with no filters or paging. GET only; to copy it into another system, poll it and skip the ids you already have. |
/api/users | Members and service accounts. ?service_user=true lists only service accounts, false only members. POST invites a member by email, or creates a service account with "is_service_user": true. /{userId} answers PUT (role, auto-assigned groups, blocked) and DELETE, but not GET; /{userId}/invite sends an invitation again. |
/api/users/me | The member who makes the call, with their role and permissions. GET only. A service account's token gets 403 here; to check such a token, call GET /api/organizations instead. |
/api/users/{userId}/api-tokens | Access tokens of a member or service account. POST with name and expires_in (days, 1 to 365) answers with plain_token, shown this once only. A token's own path answers GET and DELETE. |
/api/organizations | The organization the call acts in, as a list with one entry (GET). PUT /api/organizations/{organizationId} changes its settings; see the example. |
/api/geo/countries | Country codes, and under /{country}/cities the cities of one country, for device requirements that check location. GET only. |
Conventions
- Identifiers are opaque strings such as
p64dm3gk7ku6rc5asbde. Read them from the API; do not parse them or make them up. - References are by ID. An access rule names group IDs, not group names, so look the groups up first. An ID that names no group in the organization is left out of the rule rather than refused, so check that every lookup found one.
- Bodies are strict. A request body may hold only the fields its schema lists, at every depth. Any other field is refused with 400 and a message that names it.
- PUT replaces. Build the body from what
GETreturned: keep every field the request accepts, change the ones you mean to change, and leave out fields the request does not list, such asid.
Errors
Every error has the same body: {"message": "...", "code": 403, "error_code": "..."}. code repeats the HTTP status, and message is written for people. error_code is present only where a client has to tell failures apart; branch on it, never on the text of message.
| Status | Meaning |
|---|---|
| 400 | The body is not valid JSON, holds a field the request does not accept (the message names it), or has a field of the wrong type. |
| 401 | The token is missing or refused. error_code says why: authentication_required (no Authorization header, or an unknown scheme), token_expired or token_invalid. |
| 403 | The role of the token's account does not allow the operation. A member's own password, two-factor, passkey and profile settings refuse access tokens with interactive_session_required; change those in the dashboard. |
| 404 | The object does not exist in this organization, or no endpoint has this path (endpoint not found). |
| 405 | The path exists, but not for this method, such as GET on /api/users/{userId} (method not allowed for this endpoint). |
| 409 | The object conflicts with one that exists, such as a group name that is already taken. |
| 412 | A precondition is not met, such as deleting a device requirement that an access rule still uses, or a device that is a routing device of a network. |
| 422 | A value is invalid, such as an empty name, a port outside 1 to 65535 or a token lifetime outside 1 to 365 days. |
| 429 | Too many attempts. Only sign-in (/api/auth/) and a member's own security settings are rate-limited, and neither is used with an access token. Wait before you retry. |
| 500 | The server could not handle the request. Retry later, and if it keeps failing, contact support. |
Examples
The examples run in a POSIX shell with curl and jq. Set the token and two shorthands once, then check that the token works; the token comes from your secret store. The check prints 200 when the token works, and 401 when it is missing, expired or wrong.
export SXACCESS_TOKEN='sxp_...'API=https://app.sxaccess.com/apiAUTH="Authorization: Token $SXACCESS_TOKEN"curl -sS -o /dev/null -w '%{http_code}\n' "$API/organizations" -H "$AUTH"List devices
The list holds every device in the organization, with its overlay address and whether it is connected right now.
curl -sS "$API/devices" -H "$AUTH" | jq '.[] | {id, name, ip, connected}'curl -sS "$API/devices?name=db" -H "$AUTH" | jq -r '.[].name'Ask what a device can reach
Working out from the access rules what a device can reach is error-prone. /api/devices/{deviceId}/reachable-devices gives the answer after every access rule and device requirement has been evaluated: the devices this device may connect to and the devices allowed to connect to it, including the routing devices that carry its traffic to network resources. Protocols and ports are not part of the answer; the access rules decide those.
Set DEVICE to the device's id from GET /api/devices. The second example exits with a non-zero status unless db-01 is in the answer, which makes this endpoint the right thing to check in a test.
DEVICE=mv5ovp30rnue20llr362curl -sS "$API/devices/$DEVICE/reachable-devices" -H "$AUTH" \ | jq '.[] | {name, ip, connected}'curl -sS "$API/devices/$DEVICE/reachable-devices" -H "$AUTH" \ | jq -e 'any(.[]; .name == "db-01")' > /dev/nullCreate an access rule
This lets Engineering reach Production databases on TCP 5432, one way. It needs a token whose role may change access rules: owner, admin or network admin. The rule refers to groups by ID, so the first block looks both groups up and prints their IDs. Check that neither is empty: an ID that names no group is left out of the rule rather than refused.
"bidirectional": false makes the rule one-way; true also lets the destinations open connections to the sources. Add source_device_requirements with the IDs of device requirements to require them of the sources. Afterwards, ask what a device in Engineering can reach to confirm the rule does what you meant.
SRC=$(curl -sS "$API/groups" -H "$AUTH" | jq -r '.[] | select(.name == "Engineering") | .id')DST=$(curl -sS "$API/groups" -H "$AUTH" | jq -r '.[] | select(.name == "Production databases") | .id')echo "from $SRC to $DST"curl -sS -X POST "$API/access-rules" -H "$AUTH" \ -H 'Content-Type: application/json' --data-binary @- <<EOF | jq .{ "name": "Engineering to production databases", "description": "TCP 5432, one way", "enabled": true, "rules": [ { "name": "Engineering to production databases", "enabled": true, "action": "accept", "protocol": "tcp", "ports": ["5432"], "bidirectional": false, "sources": ["$SRC"], "destinations": ["$DST"] } ]}EOFChange organization settings
PUT /api/organizations/{organizationId} takes the organization's settings as one object. Read them with GET, change what you mean to change, and send the complete settings object back. This example sets the Session length of devices enrolled by signing in (Settings → Security; it applies while Expire device sessions is on) to 12 hours. It needs an owner's or an admin's token. The first block reads the organization the token acts in. The second sends the complete settings object back with one change: device_login_expiration is the session length in seconds, so 43200 is 12 hours.
curl -sS "$API/organizations" -H "$AUTH" > organization.jsonORG_ID=$(jq -r '.[0].id' organization.json)jq '{settings: (.[0].settings | .device_login_expiration = 43200)}' organization.json \ | curl -sS -X PUT "$API/organizations/$ORG_ID" -H "$AUTH" \ -H 'Content-Type: application/json' --data-binary @- | jq .Terraform
If you manage SXAccess with Terraform, its provider calls this same API, so treat it like any other client: a service account and a token of its own, with the narrowest role that covers what it manages. Keeping groups, access rules, networks, routes and DNS in code gives you review before a change, history after it, and a quick answer to what changed when something breaks.

