SSyncropel Docs

Service Accounts and Tokens

Bootstrap the first service account on a fresh install, save the token, and switch the daemon to secure mode. Create additional scoped accounts later. Rotate, revoke, and manage tokens.

This page takes a daemon that just came up in dev mode and turns it into a fully-secure local daemon. The full round-trip is four commands; budget three minutes.

Why authentication is on by default

spl serve enforces auth.required = true out of the box. Every HTTP request must carry a valid bearer token. The reason is simple: any daemon reachable from any other machine — over LAN, a private network, a tunnel, a reverse proxy, or an accidentally-bound 0.0.0.0 — would be catastrophic without auth. Defaulting to required closes that hole on first run.

The side effect is that a fresh install cannot start a secure daemon directly — there is no token yet, and no service account to mint one from. The bootstrap flow below is the designed way through this.

The bootstrap flow

Four commands, run in order. Replace $(whoami) with your actual username if you're not on a POSIX shell.

# 1. Start in dev mode so the daemon comes up at all.
spl serve --daemon --insecure-localhost

# 2. Bootstrap the first service account, bound to your user DID.
spl service-account create --bootstrap \
  --name admin \
  --actors did:sync:user:$(whoami) \
  --scopes admin \
  --with-token

# 3. Save the token (shown once in step 2's output).
spl token save spl_prod_sa_...

# 4. Switch to secure mode.
spl serve --stop
spl serve --daemon

After step 4, the daemon is bound to 127.0.0.1:9100, enforcing auth, and every spl CLI command works because the token you saved in step 3 is automatically injected into outbound requests.

What each flag does

FlagMeaning
--bootstrapRoutes to the privileged endpoint that works without existing auth. Only succeeds when the namespace has zero service accounts — the "first SA" escape hatch.
--name adminDisplay name for the service account. Purely cosmetic; used in spl service-account list.
--actors did:sync:user:$(whoami)The DID allowlist for this service account. The token will only succeed when the caller claims to be this actor. For the first account, pass your own DID so the CLI can use this token.
--scopes adminCapability grant. admin is the superset; narrower scopes cover individual surfaces (see below).
--with-tokenMint a plaintext token in the same response. Without this flag, the account is created but no token is issued — you'd need spl token mint afterwards.

If you omit --actors

The command will still succeed, but the service account will have no actor allowlist. A token minted against it will not authorize any record writes (the DID claim check will fail on every request). The daemon will start in secure mode, but your CLI won't be able to do anything with it. Recovery: revoke the broken account (spl service-account revoke <id>) and redo the bootstrap with --actors set correctly.

Save the token

Step 2's output includes a plaintext token that begins with spl_prod_sa_. It is shown once — copy it now.

spl token save spl_prod_sa_XXXXXXXX_YYYYYYYYYYYY

This writes ~/.syncro/token with mode 0600. Every spl command from now on reads that file and injects it as the Authorization: Bearer header.

The CLI's token discovery precedence:

  1. --token <value> — per-command override.
  2. SPL_TOKEN environment variable — useful in CI.
  3. ~/.syncro/token — the persistent default.

spl token show-source reports which source the CLI is currently using, with the token masked.

The scope system

A closed enum of seven capabilities. Tokens carry a snapshot at issuance time — changes to a service account's scopes don't apply to existing tokens, only to newly minted ones.

ScopeGrants
records:readGET /v1/records, GET /v1/threads/*
records:writePOST /v1/records
threads:writePOST /v1/threads, DELETE /v1/threads/{id}
federation:manageAll /v1/sync/*, /v1/federation/*, /v1/discovery/*
config:readGET /v1/config/*
config:writePOST /v1/config/*
adminEverything, including service account and token mutations

Use the narrowest scope that covers the use case. A Grafana dashboard that reads metrics needs records:read. A phone client that emits observations needs records:write. A federation peer needs federation:manage. Only the bootstrap account needs admin.

Creating additional scoped accounts

Once the first account exists and you've saved its token, subsequent accounts are created without the --bootstrap flag:

spl service-account create \
  --name "grafana" \
  --actors did:sync:svc:grafana \
  --scopes records:read \
  --with-token

The command uses your admin token to authenticate. The response includes a fresh token for the new account.

Listing, rotating, revoking service accounts

# Show every service account and its scopes.
spl service-account list

# Rotate — mint a new token then revoke the old one. Partial-failure-safe:
# if rotation succeeds but old-revoke fails, you have both tokens live.
# Mop up with `spl token revoke` manually.
spl token rotate <sa_id>

# Revoke a single token. The service account survives; other tokens survive.
spl token revoke --sa <sa_id> --token-id <token_id>

# Revoke the entire service account. Every token ever issued is killed;
# no future tokens can be minted for this SA. This is irreversible.
spl service-account revoke <sa_id>

Which revocation to use

ScenarioCommand
Scheduled rotation / time-based policyspl token rotate <sa_id>
One phone lost; other devices still trustedspl token revoke --sa <sa_id> --token-id <token_id>
Laptop stolen; service account keys may be leakedspl service-account revoke <sa_id>
Retiring an identity entirely (personnel change, project end)spl service-account revoke <sa_id>

Listing tokens and their origins

spl token list

Shows every token issued, with the service account it belongs to, the creation time, and a masked prefix. Useful for auditing which tokens exist and cross-referencing with your device inventory.

If you lose the bootstrap token

The --bootstrap escape hatch only works when the namespace has zero service accounts. Once one exists, the endpoint is closed. If you've lost the bootstrap token but still have shell access to the host:

spl serve --stop
spl serve --daemon --insecure-localhost
spl service-account list                   # confirm the SA exists
spl token rotate <sa_id>                   # mint a fresh token on the existing SA
spl token save spl_prod_sa_...             # save it
spl serve --stop
spl serve --daemon

--insecure-localhost bypasses auth on loopback so you can recover.

See also

On this page