sondahub

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

These examples share one session: run them in order and each sees what the one before it did.
Who the token is
curl "https://api.sondahub.com/sandbox/doppler/v3/me" \
  -H "Authorization: Bearer dp.pt.sondahubSandboxPersonalToken"
A service token reads its config: no project or config needed
curl "https://api.sondahub.com/sandbox/doppler/v3/configs/config/secrets/download?format=env" \
  -H "Authorization: Bearer dp.st.dev.sondahubSandboxServiceToken"
Raw and computed values
curl "https://api.sondahub.com/sandbox/doppler/v3/configs/config/secrets?project=backend&config=dev" \
  -H "Authorization: Bearer dp.pt.sondahubSandboxPersonalToken"
A branch config overrides its root
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"
Set secrets
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"}}'
A reference that doesn’t resolve is refused
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}"}}'
What changed
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

ProjectConfigsWhat it is
backenddev, stg, prd, dev_personalThe API and its workers
webdev, stg, prdThe 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

GET/v3/configs/config/secretsAnd POST (secrets or change_requests), /secret (get, delete), /secrets/names, /secrets/download, /secrets/note.
GET/v3/configsAnd POST (branch configs), /configs/config: get, rename, delete, clone, lock, unlock.
GET/v3/configs/config/logsAnd /logs/log, /logs/log/rollback.
POST/v3/configs/config/tokensService tokens: list, create (read or read/write, expiry), delete.
GET/v3/projectsAnd create, get, update, delete; /v3/environments; /v3/me; /v3/workplace.

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.