Sandbox ✅
Engage with LifeCare.ID without requesting access. The sandbox is an isolated instance where you self-register an app and mint a synthetic signed-in user in seconds, then run the full sign-in to grant to token to introspect loop. It holds only synthetic, zero-PHI data, and it is separate from production.
- Base URL: your operator's sandbox host (for example
https://sandbox.lifecare.id), or a local instance you run yourself (see Run it locally). - No operator in the loop: registration and test users are self-service here. Production clients stay operator-provisioned for now (see the roadmap).
- One-time access: the hosted sandbox requires a developer access token (enter
your email + accept the terms, click the magic link). Two hosts are
involved: the token comes from the LifeCare.ID® portal at
https://app.lifecare.id/devaccess, and you spend it against the sandbox at
https://sandbox.lifecare.id. The portal's verify page shows the token to use as a Bearer for CLI/CI, and sets thelc_devcookie for the browser. The sandbox host cannot email links itself; its own/devaccesssends you to the portal. A sandbox you run locally has no gate. - Three endpoints, all sandbox-only (they return
404on a production instance):POST /v1/sandbox/clients,DELETE /v1/sandbox/clients/{client_id}andPOST /v1/sandbox/test-identity.
1. Register your app (self-service)
On the hosted sandbox, pass your developer access token (from the portal, https://app.lifecare.id/devaccess) as a Bearer. (A local sandbox you run yourself needs no token.)
DEVACCESS=… # the token from https://app.lifecare.id/devaccess (the portal's verify page)
curl -s -X POST "$SANDBOX/v1/sandbox/clients" \
-H "Authorization: Bearer $DEVACCESS" \
-H "Content-Type: application/json" \
-d '{"name":"My app","redirect_uris":["http://localhost:4000/callback"]}'
{
"client_id": "sbx_Zy47k_NDW4E",
"client_secret": "qDkujP10i72…",
"redirect_uris": ["http://localhost:4000/callback"],
"note": "Sandbox client. Store the secret now - it is not shown again."
}
You now have a client_id and client_secret, the same credentials an operator
would otherwise issue you. redirect_uris is an exact-match allowlist; pass every
callback URL you will use.
Delete your app
When you are done with a client (a test run that registered one, a secret you want to retire), delete it with its own secret as the credential, as HTTP Basic. Whoever holds the secret holds the client: nobody is signed in to a sandbox client, so there is no other owner to ask.
curl -s -X DELETE "$SANDBOX/v1/sandbox/clients/$CLIENT_ID" \
-u "$CLIENT_ID:$CLIENT_SECRET"
{ "deleted": true, "client_id": "sbx_Zy47k_NDW4E" }
A wrong or missing secret, a credential for a different client, and an unknown
client_id all answer 401 alike, so the route says nothing about which ids
exist. On the hosted sandbox the Authorization header carries the client's
secret, so pass your developer access token as the lc_dev cookie instead
(--cookie "lc_dev=$DEVACCESS"); a local sandbox needs neither.
What the deletion ends is the credential: the client can no longer exchange an authorization code, sign in a user or authenticate a server-to-server call, and its redirect URIs leave the allowlist. What stays is the member's: a grant that names the client as grantee and the ledger entries about it are the member's records, and a data-access token already issued against such a grant runs to its expiry (it is bound to the grant, not to the client's secret; revoke the grant to end it early).
2. Mint a test identity (no passkey)
Production sign-in needs a real WebAuthn passkey on a device, which you cannot script. In the sandbox you can mint a synthetic signed-in user instead, so the whole loop runs headlessly (CI, curl, tests):
curl -s -X POST "$SANDBOX/v1/sandbox/test-identity" -H "Content-Type: application/json" -d '{}'
{
"did": "did:web:sandbox.example:u:817612d13f3219b6",
"session_token": "lcs_…",
"expires_at": "2026-06-24T09:00:00+00:00",
"note": "Synthetic sandbox identity (no passkey)."
}
The session_token is a real user/session token. Use it two ways:
- As
Authorization: Bearer lcs_…against the consent panel API. - As the session cookie to drive the federation flow
(
/authorize), exactly as a browser session would. On the hosted sandbox the cookie is named__Host-lc_id_session(the response'snotenames it); a local sandbox on plain http useslc_id_session. The hosted sandbox still accepts the old name for one release.
3. Run the whole loop
With a client and a test identity you can exercise everything:
# (a) act as the signed-in user: set the session cookie, then run /authorize -> /token
# (this is what the app quickstart does for you)
# (b) grant consent for a scope
curl -s -X POST "$SANDBOX/v1/consent/grant" -H "Content-Type: application/json" -d '{
"subject_did":"'"$DID"'","grantee":{"name":"My app","id":"'"$CLIENT_ID"'"},
"scopes":["cgm"],"purpose":{"text":"demo"},"duration_days":1}'
# (c) issue a data-access token, then introspect it as your client
curl -s -X POST "$SANDBOX/v1/token" -H "Content-Type: application/json" -d '{
"subject_did":"'"$DID"'","scope":"cgm","purpose":"demo","requester":"'"$CLIENT_ID"'"}'
curl -s -X POST "$SANDBOX/v1/token/introspect" -u "$CLIENT_ID:$CLIENT_SECRET" \
-H "Content-Type: application/json" -d '{"token":"lc_…"}'
Introspection is the one call here that needs your client credentials, the
client_id and client_secret from step 1 (HTTP Basic, as -u sends them). It
follows RFC 7662, section 2.1: the endpoint authenticates the resource server that
asks, so holding a token string is not enough to read whose data it opens. No
credential is 401; a member's session or a holder signature in place of a client
credential is 403. Both come before the token is looked up, on the sandbox as in
production. See
Authorization & tokens § Who may introspect.
The quickstart repos run this end to end. Point them at your sandbox base URL and the credentials from step 1.
4. Read synthetic data under that consent
A token needs something to open. The sandbox data source honours sandbox tokens and serves synthetic health data: five personas, interpreted outputs only, over REST and over MCP. It introspects every request with its own client credentials, records every read in the ledger, and stops on the call after you revoke. Its page has the walkthrough from registering a client to watching a revoked token fail.
What the sandbox is (and isn't)
- Isolated and synthetic. Separate instance, separate state, zero PHI. Nothing here touches production data or identities. The health data you can read here comes from the sandbox data source, and it is synthetic too.
- Not a passkey bypass for production. The test-identity path exists only on a
sandbox instance; a production instance returns
404for it. Production sign-in is always a real passkey, and an app can never act as a user (see Authorization & tokens). - For building and testing, not for real users or real data. When you are ready for production, you move to operator-provisioned credentials (self-service production registration is on the roadmap).
Run it locally
The sandbox is the same service with one flag. To run your own:
LIFECAREID_SANDBOX=true \
LIFECAREID_STATE_DIR=/tmp/lifecareid-sandbox \
LIFECAREID_DOMAIN=sandbox.local \
uv run uvicorn --factory lifecareid:create_app --port 8821
# → http://localhost:8821 (set SANDBOX=http://localhost:8821 for the curls above)
Operators host a shared sandbox by running this profile as a separate instance with
isolated state; the production instance leaves LIFECAREID_SANDBOX unset, so it never
exposes these routes.