Skip to content
slopscale
Esc
↑↓navigate↵open⌘Jpreview
On this page

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, with status, title and detail, and the usual meaning of the status: 400 for malformed input, 401 and 403 for a missing or insufficient credential, 404 for an unknown user or node, 409 for a duplicate name, 412 for a stale If-Match. An unexpected failure answers 500 with 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/user
curl -H "Authorization: Bearer <API_KEY>" \
    https://slopscale.example.com/api/v1/user?name=bob
curl -H "Authorization: Bearer <API_KEY>" \
    --json '{"user": "<USER>", "authId": "<AUTH_ID>"}' \
    https://slopscale.example.com/api/v1/auth/register

Tailscale-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 a 404. The tailnet ID is made on the server’s first start and never changes. It has the shape of a Tailscale tailnet ID, such as T4bXk29QaZmCNTRL. It is shown on the console’s Settings › Tailnet page and as tailnetId in GET /api/v1/server. A machine whose client knows the field (capability version 148 and later) reports it as CurrentTailnet.StableID in tailscale 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 its LOGIN_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:

  1. Swap the prefix: hskey-client-… becomes tskey-client-…. The client only runs the exchange for tskey-client-; Slopscale accepts both.
  2. Append ?baseURL=<your Slopscale URL>.
  3. Set --advertise-tags. Each tag must exist in the policy’s tagOwners and 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

  1. Download the slopscale binary from GitHub’s release page. Make sure to use the same version as on the server.

  2. Put the binary somewhere in your PATH, e.g. /usr/local/bin/slopscale

  3. Make slopscale executable: chmod +x /usr/local/bin/slopscale

  4. Create an API key on the Slopscale server.

  5. 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 slopscale binary 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 be https.

  6. Test the connection by listing all nodes:

    slopscale nodes list

    You 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: true in the configuration file or by setting SLOPSCALE_CLI_INSECURE=1 via an environment variable. We do not recommend to disable certificate validation.

Last updated on October 1, 2026

Was this page helpful?