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 --daemonAfter 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
| Flag | Meaning |
|---|---|
--bootstrap | Routes to the privileged endpoint that works without existing auth. Only succeeds when the namespace has zero service accounts — the "first SA" escape hatch. |
--name admin | Display 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 admin | Capability grant. admin is the superset; narrower scopes cover individual surfaces (see below). |
--with-token | Mint 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_YYYYYYYYYYYYThis 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:
--token <value>— per-command override.SPL_TOKENenvironment variable — useful in CI.~/.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.
| Scope | Grants |
|---|---|
records:read | GET /v1/records, GET /v1/threads/* |
records:write | POST /v1/records |
threads:write | POST /v1/threads, DELETE /v1/threads/{id} |
federation:manage | All /v1/sync/*, /v1/federation/*, /v1/discovery/* |
config:read | GET /v1/config/* |
config:write | POST /v1/config/* |
admin | Everything, 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-tokenThe 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
| Scenario | Command |
|---|---|
| Scheduled rotation / time-based policy | spl token rotate <sa_id> |
| One phone lost; other devices still trusted | spl token revoke --sa <sa_id> --token-id <token_id> |
| Laptop stolen; service account keys may be leaked | spl 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 listShows 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
- Pairing a browser or phone — next page: device-scoped accounts with
spl pair - Authentication and service accounts — the full reference, including emergency escape hatches
- Exposing the daemon securely — what to do once auth is on and you want external clients
Starting Your Instance
The three modes `spl serve` runs in — dev, secure local, and exposed — and how to manage your instance's lifecycle. Start, stop, restart, inspect, tail logs.
Pairing a Browser or Phone
Use `spl pair` to attach a browser, phone, or other device to your instance with its own scoped service account and token. One-click URL, QR-code flow, manual pairing for headless hosts, and revocation.