API
Create and scope API keys, call the REST API, use the Tailscale-compatible API with OAuth clients, Terraform and Kubernetes, and run the CLI remotely.
Slopscale provides a HTTP REST API which drives the built-in admin console, may be used to remote control Slopscale or provide a base for custom integration and tooling.
The API requires a valid API key before use. To create an API key, log into your Slopscale server and generate one with the default expiration of 90 days:
slopscale apikeys create
Copy the output of the command and save it for later. An API key cannot be retrieved again. If the API key is lost, expire the old one, and create a new one.
A key created this way is all-access. To hand out less, create the key for a user, so it is bounded by the user’s role:
slopscale apikeys create --user <USER>
A key can also be limited to some operations with scopes, the same vocabulary the OAuth clients use, and carry a description saying what it is for. The scopes never reach past the caller that mints the key or the role of the user that owns it: a scope the caller cannot delegate is dropped, and a caller that can delegate none of them is refused. A key without scopes keeps its owner’s whole role, unless the caller is itself a scoped key, in which case the new key inherits the caller’s scopes. An OAuth access token cannot mint API keys at all: a token is bounded by tags as well as scopes, and an API key has no tags to be bounded by.
slopscale apikeys create --user <USER> --scope dns --scope devices:core:read --description "Resolver sync"
The console’s Keys page offers the same when creating an API key, and lists each key’s scopes and description.
To list the API keys currently associated with the server:
slopscale apikeys list
and to expire an API key:
slopscale apikeys expire --prefix <PREFIX>
A key can be rotated instead of replaced. Rotating mints a new secret for the same key and prints it once; the key keeps its id, owner, scopes, description and expiry, and the old secret stops working immediately:
slopscale apikeys rotate --prefix <PREFIX>
Pass --expiration to give the rotated key a new expiry; without it the key
keeps the one it has. An expired key cannot be rotated, because expiring a key
is how it is revoked, so create a new key instead. A key that carries its own
scopes may only rotate a key whose scopes it could have minted itself, so
rotation never widens a credential. The console’s Keys page offers the same
action, and every rotation is recorded in the audit log as apikey.rotate,
naming the prefix the key had and the one it now answers to.
REST API
- API endpoint:
/api/v1, e.g.https://slopscale.example.com/api/v1 - Documentation:
/api/v1/docs, e.g.https://slopscale.example.com/api/v1/docs - Slopscale Version:
/version, e.g.https://slopscale.example.com/version - Authenticate using HTTP Bearer authentication by sending the API key with the HTTP
Authorization: Bearer <API_KEY>header. - The OpenAPI 3.1 document is at
/api/v1/openapi.yaml. - Errors are RFC 7807 problem details,
application/problem+json, withstatus,titleanddetail, and the usual meaning of the status:400for malformed input,401and403for a missing or insufficient credential,404for an unknown user or node,409for a duplicate name,412for a staleIf-Match. An unexpected failure answers500with an id that finds it in the server log.
Start by creating an API key and test it with the examples below. Read the API documentation provided by your
Slopscale server at /api/v1/docs for details.
curl -H "Authorization: Bearer <API_KEY>" \
https://slopscale.example.com/api/v1/usercurl -H "Authorization: Bearer <API_KEY>" \
https://slopscale.example.com/api/v1/user?name=bobcurl -H "Authorization: Bearer <API_KEY>" \
--json '{"user": "<USER>", "authId": "<AUTH_ID>"}' \
https://slopscale.example.com/api/v1/auth/registerTailscale-compatible API
Alongside /api/v1, Slopscale serves a second API at /api/v2 that reuses
Tailscale’s wire shapes, so tooling written against Tailscale’s control
plane works against Slopscale unchanged: the
Terraform/OpenTofu provider,
tscli, the official
Go client and the
Kubernetes operator.
- API endpoint:
/api/v2, e.g.https://slopscale.example.com/api/v2 - Documentation:
/api/v2/docs - The tailnet in every path is
-or the tailnet ID; Slopscale serves one tailnet, and any other name is a404. The tailnet ID is made on the server’s first start and never changes. It has the shape of a Tailscale tailnet ID, such asT4bXk29QaZmCNTRL. It is shown on the console’s Settings › Tailnet page and astailnetIdinGET /api/v1/server. A machine whose client knows the field (capability version 148 and later) reports it asCurrentTailnet.StableIDintailscale status --json. - Authenticate with an API key, which is all-access, or with
OAuth 2.0 client credentials, which are limited to the
scopes and tags they were created with. Point the Terraform provider at
Slopscale with its
base_url, and the operator with itsLOGIN_SERVER/--login-server.
OAuth clients
Most of the ecosystem accepts an API key or OAuth client credentials; the
Kubernetes operator accepts only the latter. An OAuth client is a key with
keyType: "client", created with slopscale oauth-clients create, on the
console’s Keys page, or through the API. Its secret is shown once. The client
exchanges it for a one-hour access token at POST /api/v2/oauth/token, and the
token carries the client’s scopes and tags:
$ slopscale oauth-clients create --scope devices:core --scope auth_keys \
--tag tag:k8s-operator --description "Kubernetes operator"
The scopes bound which operations the token may perform, and the tags bound
which machines it may create: an auth key minted with the token may only carry
tags the token holds, or tags those own through the policy’s tagOwners. A
token cannot mint API keys, and it can never be given more than the caller that
created the client could delegate.
Join nodes with an OAuth client
The Tailscale client can use an OAuth client secret in place of an auth key: it
exchanges the secret for an access token, mints a single-use tagged auth key and
registers with it. This works anywhere the client takes an auth key:
tailscale up, the container image, tsnet and the
tailscale/github-action. One
long-lived secret joins any number of nodes.
Create an OAuth client with the auth_keys scope and the tags its nodes get.
The secret is shown once:
slopscale oauth-clients create --scope auth_keys --tag tag:ci
Build the auth key from the secret:
- Swap the prefix:
hskey-client-…becomestskey-client-…. The client only runs the exchange fortskey-client-; Slopscale accepts both. - Append
?baseURL=<your Slopscale URL>. - Set
--advertise-tags. Each tag must exist in the policy’stagOwnersand be one of the OAuth client’s tags, or owned by one of them.
tskey-client-<id>-<secret>?baseURL=https://slopscale.example.com
These are all the attributes the client understands; any other is an error. Order does not matter and an empty value means the default.
| Attribute | Default | Effect |
|---|---|---|
baseURL |
https://api.tailscale.com |
Where the exchange and key creation go. Your Slopscale URL, no trailing slash. |
ephemeral |
true |
The node is removed after it goes offline (node.ephemeral.inactivity_timeout). false keeps it. |
preauthorized |
false |
While device approval is on, a node joined with false waits for approval and one joined with true does not. |
Booleans take any Go strconv.ParseBool value (true, false, 1, 0, …).
tailscale up, either as the auth key or through --client-secret (which also
takes file:/path/to/secret):
tailscale up --login-server https://slopscale.example.com --advertise-tags tag:ci \
--auth-key 'tskey-client-<id>-<secret>?baseURL=https://slopscale.example.com&ephemeral=false'
The container image:
docker run -d --name tailscale \
-e TS_AUTHKEY='tskey-client-<id>-<secret>?baseURL=https://slopscale.example.com' \
-e TS_EXTRA_ARGS='--login-server=https://slopscale.example.com --advertise-tags=tag:ci' \
tailscale/tailscale
tsnet (TS_CLIENT_SECRET works too), with tailscale.com/feature/oauthkey
imported:
srv := &tsnet.Server{
ControlURL: "https://slopscale.example.com",
AuthKey: "tskey-client-<id>-<secret>?baseURL=https://slopscale.example.com",
AdvertiseTags: []string{"tag:ci"},
}
The GitHub Action, with the whole string stored as a repository secret:
- uses: tailscale/github-action@v4
with:
authkey: ${{ secrets.SLOPSCALE_AUTHKEY }}
args: --login-server=https://slopscale.example.com --advertise-tags=tag:ci
Use authkey even though the action marks it deprecated: oauth-secret
appends its own ?… to the secret, which corrupts baseURL.
Terraform provider support
Every resource and data source the provider offers, and how far Slopscale carries it.
| Resource | Support | Notes |
|---|---|---|
tailscale_acl |
Supported | Reads and writes the policy as HuJSON, comments and all, with an ETag for the conditional write. The plan-time validation call is supported, including running policy tests. |
tailscale_aws_external_id |
Unsupported | Slopscale has no AWS log integration, so there is no external ID to hand out. Returns 404. |
tailscale_contacts |
Unsupported | Slopscale sends no account, support or security email of its own, so it stores no contact addresses. Returns 404. |
tailscale_device_authorization |
Supported | Maps onto device approval. Note that approval must be switched on for the resource to mean anything. |
tailscale_device_key |
Supported | Sets whether a machine’s key expires. |
tailscale_device_subnet_routes |
Supported | Approves the routes a machine advertises. |
tailscale_device_tags |
Supported | Tags and users are exclusive here, so tagging a user’s machine hands it over to the tags. |
tailscale_dns_configuration |
Partial | Every field is applied, including useWithExitNode and overrideLocalDNS. magicDNS comes from the configuration file, so a plan that changes it is refused with a 400. |
tailscale_dns_nameservers |
Supported | |
tailscale_dns_preferences |
Partial | The only preference is magicDNS, which is set in the configuration file; a plan that repeats the value in force succeeds, one that changes it is refused. |
tailscale_dns_search_paths |
Supported | |
tailscale_dns_split_nameservers |
Supported | |
tailscale_federated_identity |
Supported | See workload identity federation. |
tailscale_logstream_configuration |
Partial | configuration streams the audit log. network is refused with 404: network flow logs are not streamed, they are read from GET /api/v2/tailnet/-/logging/network (see the traffic monitor). Destinations are http, splunk, elastic, datadog, axiom and loki; S3 and Cloud Storage are refused by name. |
tailscale_oauth_client |
Supported | Create, read, update in place and revoke. |
tailscale_posture_integration |
Partial | falcon, sentinelone, intune, jamfpro, kandji and kolide are wired to Slopscale’s posture integrations. fleet and huntress are refused by name. |
tailscale_service |
Supported | Maps onto Tailscale Services. |
tailscale_tailnet_key |
Supported | Slopscale’s pre-auth keys. DELETE revokes; the key stays readable as invalid until it is reaped. |
tailscale_tailnet_settings |
Partial | devicesApprovalOn, usersApprovalOn, postureIdentityCollectionOn and devicesKeyDurationDays can be changed. The rest are read-only here because they come from the configuration file or do not exist. |
tailscale_webhook |
Supported | See Webhooks. |
| Data source | Support | Notes |
|---|---|---|
tailscale_4via6 |
Supported | Computed by the provider itself with no server call, so it works against anything. |
tailscale_acl |
Supported | |
tailscale_device |
Supported | Reading a device with all fields adds its distribution, connectivity, tailnet lock key and collected serial numbers. |
tailscale_devices |
Supported | |
tailscale_service |
Supported | |
tailscale_user |
Supported | |
tailscale_users |
Supported |
Kubernetes operator
The operator authenticates only with OAuth client credentials, so create a
client with the auth_keys, devices:core and services scopes and the tag
the operator runs as, and let that tag own the tag its proxies use:
{
"tagOwners": {
"tag:k8s-operator": ["alice@"],
"tag:k8s": ["tag:k8s-operator"]
}
}
The operator then mints its own auth keys for the proxies it creates, registers a Service per ingress and deletes a proxy’s device when the workload goes away. All of it is covered by an end-to-end test in Slopscale’s own suite.
Workload identity federation
A CI job or cloud workload can trade the OIDC token its own platform signs for
a Slopscale access token, so no long-lived secret has to live in a pipeline.
Register the workload as a key with keyType: "federated", naming the issuer,
audience and subject its tokens carry, optionally with customClaimRules
it must also satisfy, and the scopes and tags it should get:
curl -H "Authorization: Bearer <API_KEY>" \
--json '{"keyType": "federated", "scopes": ["devices:core"], "tags": ["tag:ci"],
"issuer": "https://token.actions.githubusercontent.com",
"audience": "slopscale", "subject": "repo:acme/app:ref:refs/heads/main",
"customClaimRules": {"repository": "acme/app"}}' \
https://slopscale.example.com/api/v2/tailnet/-/keys
The workload exchanges its token for a one-hour access token with exactly that grant:
curl --data-urlencode "client_id=<CLIENT_ID>" --data-urlencode "jwt=$ACTIONS_ID_TOKEN" \
https://slopscale.example.com/api/v2/oauth/token-exchange
The presented token is checked against the issuer’s published keys and against
every condition the identity carries, with a minute of clock leeway. Every
exchange is recorded in the audit log as oauth.token.exchange,
refusals included, so a run of failures against one identity is visible.
The console and the CLI manage identities next to OAuth clients, so neither needs the Tailscale-compatible API. The console’s OAuth clients page creates one and edits its trust conditions in place, and so does the CLI.
$ slopscale oauth-clients create --federated --scope devices:core --tag tag:ci \
--issuer https://token.actions.githubusercontent.com --audience slopscale \
--subject "repo:acme/app:ref:refs/heads/main" --claim repository=acme/app
$ slopscale oauth-clients update --id <CLIENT_ID> \
--subject "repo:acme/app:ref:refs/heads/release"
slopscale oauth-clients list shows both kinds with the subject each identity
trusts. On /api/v1 the same operations are POST /api/v1/oauth-client with
"keyType": "federated", PATCH /api/v1/oauth-client/{clientId}, where a
field left out keeps its value and a trust condition sent for a plain client is
refused, and DELETE /api/v1/oauth-client/{clientId} to revoke.
Remote control
The slopscale binary can control a Slopscale instance from a remote machine over the HTTP API.
Prerequisite
- A workstation to run
slopscale(any supported platform, e.g. Linux). - The Slopscale server reachable over HTTP(S).
- An API key to authenticate with the Slopscale server.
Setup remote control
-
Download the
slopscalebinary from GitHub’s release page. Make sure to use the same version as on the server. -
Put the binary somewhere in your
PATH, e.g./usr/local/bin/slopscale -
Make
slopscaleexecutable:chmod +x /usr/local/bin/slopscale -
Create an API key on the Slopscale server.
-
Provide the connection parameters for the remote Slopscale server either via a minimal YAML configuration file or via environment variables:
cli: address: <SLOPSCALE_URL> api_key: <API_KEY>export SLOPSCALE_CLI_ADDRESS="<SLOPSCALE_URL>" export SLOPSCALE_CLI_API_KEY="<API_KEY>"This instructs the
slopscalebinary to connect to a remote instance at<SLOPSCALE_URL>(e.g.https://slopscale.example.com), instead of connecting to the local instance. A bare host without a scheme is assumed to behttps. -
Test the connection by listing all nodes:
slopscale nodes listYou should now be able to see a list of your nodes from your workstation, and you can now control the Slopscale server from your workstation.
Behind a proxy
The remote CLI uses the same HTTP API as everything else, so it works through the reverse proxy already in front of Slopscale with no extra setup.
Troubleshooting
- Make sure you have the same Slopscale version on your server and workstation.
- Verify that your TLS certificate is valid and trusted.
- If you don’t have access to a trusted certificate (e.g. from Let’s Encrypt), either:
- Add your self-signed certificate to the trust store of your OS or
- Disable certificate verification by either setting
cli.insecure: truein the configuration file or by settingSLOPSCALE_CLI_INSECURE=1via an environment variable. We do not recommend to disable certificate validation.