sondahub / Doppler sandbox
A Doppler API mock that doppler run can read from
Doppler’s v3 API, answered by sondahub: projects, environments, root and branch configs, secrets with raw and computed values and references resolved across configs and projects, every download format and name transformer, notes, visibility, value types, config logs with rollback and service tokens. Point the CLI’s API host here and run your app against it.
An independent imitation for testing. Not affiliated with, or endorsed by, Doppler.
Connect
- Instead of
- https://api.doppler.com
- API host
- https://api.sondahub.com/sandbox/doppler
- For the CLI
- --api-host https://api.sondahub.com
- Personal token
- dp.pt.sondahubSandboxPersonalToken (any dp.pt.…)
- Service token
- dp.st.dev.sondahubSandboxServiceToken (any dp.st.<config>.…)
- OpenAPI 3
- https://api.sondahub.com/sandbox/doppler/openapi.json
Doppler CLI
doppler run --api-host https://api.sondahub.com \ --token dp.st.dev.sondahubSandboxServiceToken -- printenv DATABASE_URL doppler secrets download --api-host https://api.sondahub.com \ --token dp.pt.sondahubSandboxPersonalToken --project backend --config dev_personal \ --no-file --format env
Python (doppler-sdk)
import requests
from dopplersdk import DopplerSDK
sdk = DopplerSDK()
sdk.set_base_url('https://api.sondahub.com/sandbox/doppler')
sdk.set_access_token('dp.pt.sondahubSandboxPersonalToken')
# carry the sandbox session from each answer to the next request (the SDK calls requests directly)
session = {}
send = requests.Session.request
def request(self, method, url, **kwargs):
if 'token' in session:
kwargs['headers'] = {**(kwargs.get('headers') or {}), 'X-Sondahub-Session': session['token']}
response = send(self, method, url, **kwargs)
session['token'] = response.headers.get('X-Sondahub-Session', session.get('token'))
return response
requests.Session.request = request
sdk.secrets.update({'project': 'backend', 'config': 'dev', 'secrets': {'QUEUE_URL': 'redis://localhost:6379/1'}})
print(sdk.secrets.get(project='backend', config='dev', name='QUEUE_URL').value)
Import it. In Sonda: Import → From a URL, paste the OpenAPI address. You get a folder per resource and a request per operation, with example bodies and names from the seed, so most requests work as they are. Then set the auth: Bearer token dp.pt.sondahubSandboxPersonalToken. Any client that imports OpenAPI 3 takes the same address.
Try it here
curl "https://api.sondahub.com/sandbox/doppler/v3/me" \ -H "Authorization: Bearer dp.pt.sondahubSandboxPersonalToken"
curl "https://api.sondahub.com/sandbox/doppler/v3/configs/config/secrets/download?format=env" \ -H "Authorization: Bearer dp.st.dev.sondahubSandboxServiceToken"
curl "https://api.sondahub.com/sandbox/doppler/v3/configs/config/secrets?project=backend&config=dev" \ -H "Authorization: Bearer dp.pt.sondahubSandboxPersonalToken"
curl "https://api.sondahub.com/sandbox/doppler/v3/configs/config/secret?project=backend&config=dev_personal&name=LOG_LEVEL" \ -H "Authorization: Bearer dp.pt.sondahubSandboxPersonalToken"
curl -X POST "https://api.sondahub.com/sandbox/doppler/v3/configs/config/secrets" \
-H "Authorization: Bearer dp.pt.sondahubSandboxPersonalToken" \
-H "Content-Type: application/json" \
-d '{"project":"backend","config":"dev","secrets":{"QUEUE_URL":"redis://localhost:6379/1","WORKER_URL":"${API_URL}/worker"}}'
curl -X POST "https://api.sondahub.com/sandbox/doppler/v3/configs/config/secrets" \
-H "Authorization: Bearer dp.pt.sondahubSandboxPersonalToken" \
-H "Content-Type: application/json" \
-d '{"project":"backend","config":"dev","secrets":{"BROKEN":"${NOT_THERE}"}}'
curl "https://api.sondahub.com/sandbox/doppler/v3/configs/config/logs?project=backend&config=dev" \ -H "Authorization: Bearer dp.pt.sondahubSandboxPersonalToken"
From the shell, keep the token yourself: curl -i shows X-Sondahub-Session; send it back with -H "X-Sondahub-Session: …". How sessions work.
The seed workplace
| Project | Configs | What it is |
|---|---|---|
backend | dev, stg, prd, dev_personal | The API and its workers |
web | dev, stg, prd | The web front end |
Every config has DOPPLER_PROJECT, DOPPLER_ENVIRONMENT and DOPPLER_CONFIG. DATABASE_URL is built from references to the DB_ secrets, staging’s Stripe key points at dev’s, and the web project reads its API URL from the backend project. prd is locked; dev_personal overrides LOG_LEVEL. Downloads come as json, env, env-no-quotes, yaml, docker or dotnet-json, with the name transformers camel, upper-camel, lower-snake, lower-kebab, tf-var, dotnet and dotnet-env.
Every value in the seed is fake and made up when the site is built. What you write is kept in your own session token and nowhere else — but this is a public playground: never send it a real secret. Not affiliated with, or endorsed by, Doppler.
What it answers
Errors are Doppler’s: {"messages": […], "success": false}.
Questions
Is this Doppler?
No — an independent imitation of Doppler’s v3 API for testing, not affiliated with or endorsed by Doppler. No workplace or account is involved.
Will the Doppler CLI and SDKs work against it?
The Python set-up on this page was run against the sandbox with Doppler’s Python SDK: projects, configs, listing secrets with their computed values, setting one and reading it back. The CLI lines use its documented --api-host; give it https://api.sondahub.com, since the sandbox answers Doppler’s /v3 paths at the root when the token starts with dp.. The CLI can’t carry the session token between commands, so doppler run reads the seed — which is what it is for.
Which tokens work?
A personal token — dp.pt.sondahubSandboxPersonalToken or any dp.pt.… — sees every project and config. A service token — dp.st.<config>.…, like dp.st.dev.sondahubSandboxServiceToken — reads its config without a project or config parameter (the first project with that config), and nothing else. Tokens you create with POST /v3/configs/config/tokens carry their project and access: a read-only one is refused on writes. Bearer, or Basic with the token as the user.
How are references resolved?
As Doppler resolves them: ${NAME} in the same config, ${config.NAME} (or the environment’s root config) in the same project, ${project.config.NAME} across projects — each answer has the raw value and the computed one. A write whose references don’t resolve is refused and nothing changes. A branch config inherits its root and overrides what it sets.
What is not modelled?
Integrations and syncs, dynamic secrets, webhooks, groups, roles and members, trusted IPs, and the audit API. Restricted secrets are labeled restricted and still returned.