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, MySQL, Redis, AWS, custom, API-key or 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.
# Rotating a TillDev token? Kill the old one once nothing uses it:
tilldev secrets linked revoke FORGE_TOKEN --env prodpull() — 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), or with tilldev secrets backends create --type <type> --config-stdin. The CLI reads the config from stdin so an admin password stays out of 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. |
mysql | The same for MySQL or MariaDB: a user per lease with your grants, dropped at expiry. See MySQL. |
redis | A Redis or Valkey ACL user per lease with your rules. See Redis. |
aws_iam | AWS credentials per lease: an STS role session, or a throwaway IAM user and key. See AWS. |
custom | Anything else: your HTTPS endpoint mints and revokes, TillSecrets signs each call and tracks the lease. See Custom. |
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. |
forge_token | A CI job or agent needs to push or clone for a while: a TillForge token per lease. See TillForge tokens. |
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), with tilldev secrets declare <KEY> --env <env> --backend <id>, 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_… (the upstream user, role or key is dropped; self-issued
# creds are marked revoked + your revoke webhook, if set, is called.)
# ! … not confirmed dropped yet (the upstream was unreachable; retried until it is)
# Admins: every lease in the workspace, or only those still waiting on a drop.
tilldev secrets leases --pending
tilldev secrets leases revoke ls_…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. A lease whose upstream drop wasn’t confirmed shows Drop pending until a retry gets through; the credential may work until then.
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).
# Pipe the config so the provider token stays out of shell history; or omit it to be prompted.
printf %s '{"account_id":"…","api_token":"…"}' | \
tilldev secrets sync add --project web --env prod --provider cloudflare --config-stdin
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 STRIPE_KEY --env prod # removes it from pulls; version history is kept
# Holds a TillDev token (tfp_, ts_, tp_…)? Revoke it at the source too:
tilldev secrets rm FORGE_TOKEN --env prod --revoke-linkedSee Config store for versioning and soft-delete behaviour in full, and revoking TillDev tokens on delete or rotation.
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.
Restrict one secret
Give a secret tier 7 or above and it is restricted, even in an environment that isn’t. Project-wide tokens stop receiving it (it is listed in withheld), only the owner can reveal it, and TillForge CI gets it on protected branches only. You can also set the tier from the console, next to the secret.
tilldev secrets update STRIPE_SECRET_KEY --env prod --sensitivity 9
# → STRIPE_SECRET_KEY tier → 9 (effective 9)
# The app that needs it gets its own token, pinned to prod and to that key.
tilldev secrets tokens create --project payments --env prod --keys STRIPE_SECRET_KEY --quietLowering a tier below 7 needs the owner. Sensitivity tiers has every rule.
Ask a person for a value
When a script or an agent needs a credential that someone else has, don’t ask them to paste it into a chat. Send a request: they fill it in their browser, and you get a handle.
REF=$(tilldev secrets request DIGITALOCEAN_TOKEN --env prod \
--note "Droplet read/write, for the job runner" --allow-host api.digitalocean.com)
# stderr shows a link and a code. Send both to the person who has the token.
echo "$REF" # secretref://infra/prod/DIGITALOCEAN_TOKENSecret requests explains what the person sees and why the code matters. For someone outside the workspace, such as a vendor, add --external for a drop link: what they send waits for you to accept it.
Let a machine make one call
A machine that needs one privileged call — a droplet deleting itself when a job ends — should not hold the credential. Presign the call, and give the machine the tsc_ token.
# Approve the call now…
tilldev secrets broker allow secretref://infra/prod/DIGITALOCEAN_TOKEN api.digitalocean.com \
--methods GET,DELETE --path /v2/droplets
tilldev secrets presign DELETE "https://api.digitalocean.com/v2/droplets?tag_name=job-42" \
--bearer secretref://infra/prod/DIGITALOCEAN_TOKEN --expires 6 --quiet > call.token
# …the machine runs it later, holding only call.token.
TILLSECRETS_CALL_TOKEN="$(cat call.token)" tilldev secrets call
tilldev secrets presigned ls # uses left, expiry
tilldev secrets presigned revoke <id>See Presigned calls for the limits and checks.
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 presigned call token (tsc_…) | tilldev secrets presigned revoke <id>. The token could only run the one call it was minted for, and only its remaining uses. |
| 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.