TillSecrets, task by task.
GAThe other pages explain how TillSecrets works. This one is the operator’s manual: find the thing you want to do, and copy the exact steps. Every command below is real and current — CLI, SDK, or dashboard, whichever is the right tool for the job.
| When you want to… | Go to |
|---|---|
| Read a secret back into an app or a file | Read a secret |
| Change a secret’s value safely | Rotate a value |
| Undo a bad change | Roll back |
| Give a service the least access it needs | Scope a token |
| Actually get a leased dynamic credential | Retrieve a lease |
| Add a Postgres / API-key / key-pair backend | Register a backend |
| Turn a key into a dynamic (leased) one | Declare a dynamic key |
| Kill a leased credential now / see live ones | Revoke & list leases |
| Mirror secrets to Cloudflare, Vercel, … | Mirror to a platform |
| Retire a secret | Delete a secret |
| See who read or leased what | Read the audit log |
| Respond to a leaked token or credential | Recover from a leak |
Read a secret back
Most of the time an app reads its whole environment at boot. The quickstart covers the SDK path; from a shell or CI you can pull straight to a .env:
# Pull the whole environment as dotenv — a read-scoped token is enough.
export TILLSECRETS_TOKEN=ts_…
tilldev secrets pull --env env_… > .env
# Read one value in an app (Node): pulled at boot, injected into process.env.
# import { load } from '@tillstack/secrets-node'; await load()
# Or reveal a single value from the CLI (this is AUDITED — use sparingly):
tilldev secrets get <secret-id>pull and load() take a read token. Revealing a single value with secrets get is recorded in the audit log — it’s for a human checking a value, not a hot path.
Change a value without downtime
Setting a key that already exists allocates a new version and makes it current — the previous value is never overwritten, so a rotation is always reversible.
# Setting a key that already exists writes a NEW version; the old one is kept.
tilldev secrets set STRIPE_KEY --env env_… --value sk_live_new
# → Set STRIPE_KEY → v4
# Secrets are pulled at boot, so a running fleet keeps the old value until it
# re-pulls. Redeploy (or call pull() on a timer) to roll the new value out.pull() — TillSecrets doesn’t push.Roll back a bad change
Every write is a version. If a value turns out wrong, promote the last-known-good one back to current — no re-typing the secret, and the rollback is itself audited:
tilldev secrets versions <secret-id> # find the good version
tilldev secrets rollback <secret-id> --version 2 # make v2 current againGive a service least-privilege access
Apps authenticate with a ts_… service token. Two rules keep the blast radius small: scope read unless the service leases dynamic credentials, and pin the token to a single environment so it can never read another.
# Read-only, pinned to ONE environment — what a service boots with.
tilldev secrets tokens create --project prj_… --scope read --env env_…
# read_write — needed ONLY to lease or revoke dynamic credentials (below).
tilldev secrets tokens create --project prj_… --scope read_write --env env_…
tilldev secrets tokens ls --project prj_…
tilldev secrets tokens revoke <token-id> --project prj_…| Scope | Can do |
|---|---|
read | Pull static secrets. The default; give this to every normal consumer. |
read_write | Everything read can, plus lease and revoke dynamic credentials. Grant only where leasing happens. |
Retrieve a leased dynamic credential
A dynamic secret has no stored value, so there is deliberately no “reveal” button anywhere in the dashboard. The credential exists only at the instant you lease it, and it is returned exactly once. You get it one of three ways.
From the CLI:
export TILLSECRETS_TOKEN=ts_… # a read_write token
tilldev secrets lease DATABASE_URL --ttl 900
# ↳ prints the lease id, the expiry, and every credential field — ONCE.From an app, with the SDK:
import { createClient } from '@tillstack/secrets-node'
const secrets = createClient() // read_write TILLSECRETS_TOKEN in env
const { leaseId, credential, expiresAt } =
await secrets.lease({ secretKey: 'DATABASE_URL', ttlSeconds: 900 })
// The credential FIELDS depend on the backend that minted it:
// postgres → { username, password, host, port, database }
// api_key → { token, credential_ref }
// keypair → { algorithm, private_key, public_key_ssh, fingerprint, … }
// Assemble your DSN / write the key file, then use it until expiresAt.Or over HTTP against the lease service directly — see Dynamic secrets → Leasing for the raw POST /v1/lease call.
read_write token.Register a dynamic backend
Backends are registered in the dashboard under Secrets → Dynamic → Register backend (admin only). There is deliberately no CLI verb — a backend can hold an admin database password, which shouldn’t pass through shell history. Pick the type:
| Type | Use it when |
|---|---|
postgres | You own a database and want each app instance to lease its own time-boxed role. Needs an admin connection. See Backends. |
api_key | You want a strong random token per lease (webhook signing, per-tenant keys). Self-issued — no upstream. See Self-issued backends. |
keypair | You need a fresh SSH / PKI key pair per lease (ed25519 or RSA). Self-issued; the private key is shown once. See Self-issued backends. |
The self-issued types (api_key, keypair) need no connection — an empty {} config accepts every default. Add an optional revoke_webhook_url to make revocation enforceable at your consumer.
Turn a key into a dynamic one
A backend can mint credentials, but an app leases them by a familiar env-var key. Declare that mapping in the dashboard (Secrets → Dynamic → Declare dynamic key), or with a session-authenticated call — the exact request is here. Once declared, the key shows up in a pull() under dynamic: [...], and lease() resolves it to that backend.
Revoke a lease early & see live ones
Every lease self-expires at its TTL, and a background sweep reaps anything that slips past. To drop one immediately:
# Hand a lease back before its TTL (needs a read_write token).
tilldev secrets lease revoke ls_…
# → Revoked lease ls_… (Postgres roles are DROP-ed; self-issued creds are
# marked revoked + your revoke webhook, if set, is called.)The dashboard’s Secrets → Leases screen (admin) is the live custody ledger — every credential currently out, from which backend, with a countdown to expiry and a one-click Revoke. It shows the credential reference only, never the material.
Mirror secrets to a platform
If a runtime can’t pull at boot, mirror an environment into its native secret store. A sync target pushes the current values to cloudflare, vercel, railway, github, aws_ssm, dotenv, or a custom webhook.
# Register a target once (provider config is envelope-encrypted).
tilldev secrets sync add --project prj_… --env env_… \
--provider cloudflare --config '{"account_id":"…","api_token":"…"}'
tilldev secrets sync push <target-id> --project prj_… # mirror now
tilldev secrets sync ls --project prj_… # status + last error
tilldev secrets sync pause <target-id> --project prj_… # stop mirroringSee Config store → Sync targets for each provider’s config shape. Mirroring is a copy — the source of truth stays in TillSecrets.
Delete a secret
Removing a key takes it out of future pulls; its version history is retained for the audit trail rather than hard-erased.
tilldev secrets rm <secret-id> # removes it from pulls; version history is keptSee Config store for versioning and soft-delete behaviour in full.
See who read or leased what
Reveals, pulls, leases, revokes, token creation and use, and backend changes are all recorded. Read them under Secrets → Audit in the dashboard, or see Security → Audit log for the event shape and retention.
Recover from a leak
Match the response to what leaked:
| What leaked | Do this |
|---|---|
| A service token (ts_…) | tilldev secrets tokens revoke <id>, then mint a fresh one. Because tokens are env-pinned and scoped, the exposure was already bounded to one environment. |
| A static secret value | Set a new value (secrets set — a new version) and rotate it at the upstream. The old version stays in history but is no longer current. |
| A leased credential | Its short TTL already limits the window. secrets lease revoke ends it now: Postgres roles are dropped; self-issued credentials are marked revoked and your revoke_webhook_url (if set) is called. With no webhook, keep TTLs short and validate the credential_ref server-side. |
New here? Start with the quickstart. Going deep on leasing? See Dynamic secrets. Want the trust model? Security. Back to the TillSecrets overview.