Security & compliance
Integrating with LifeCare.ID means inheriting — and upholding — a small set of hard guarantees. This page states what the platform guarantees you, and what you must guarantee in return.
The zero-PHI invariant (the one rule above all)
For any data value d held by a resource holder R and delivered to a requester Q, the transfer R → Q is direct. No LifeCare.ID component is an intermediary, cache, proxy, or co-processor of d. LifeCare.ID participates only in
{DID, public key, token, scope, consent receipt, revocation status}.
This is architectural, not a policy toggle. What it means for you:
- Never send a health value to a LifeCare.ID endpoint. Not in a scope, not in
a
purpose, not in anaccessor, not in any free-text field. Scopes name categories; purposes are short declared strings; descriptions are plain-language and value-free. - Data flows directly between the data source and the requesting app. If your design routes data through LifeCare.ID, it's wrong — redesign it.
- A full compromise of LifeCare.ID must yield only metadata. Keep it that way by never putting a value where one doesn't belong.
A data source must additionally be able to demonstrate direct transfer (this is part of future conformance). An app accepts the obligation as part of the Network Developer Agreement (🛣️).
Identity & key custody
- The user's private key never leaves their device (Secure Enclave / Android Keystore, via WebAuthn). You never see, store, or transmit it.
- LifeCare.ID is the DID authority, not the key holder — it cannot sign as the user, and neither can you.
- Recovery is human-anchored (email magic link / trust partner), never a
password reset. Key rotation (
/v1/identity/rotate) is fail-secure: with no trust-partner approval configured, it is refused. - Because keys can rotate, always resolve the DID document fresh when you need the user's current verification key.
Token handling
| Token | Prefix | Protect by |
|---|---|---|
| Session / user token | lcs_ |
server-side storage, TLS, never in URLs/logs; honor logout |
| Data-access token (RPT) | lc_ |
TLS, never logged; cache only to expires_at |
The lcs_ session token doubles as the user token: when a resource server
introspects it, it carries subject_did — proof the caller is that user, required
for user-self actions (e.g. in-app consent). A lc_ access token never carries a
subject; it names the data it unlocks via resource_owner. See
Authorization & tokens.
- Tokens are bearer credentials — possession is authorization. Treat them like secrets.
- Tokens are stored hashed at rest by the AS; you should not need to persist
raw
lc_tokens beyond their short lifetime. - Minimum scope, minimum lifetime. Request a single scope, only when needed.
Rate limits
A deployment can limit how fast the authentication routes are called
(LIFECAREID_RATE_LIMITS: off, log_only or enforce; off unless the operator
turns it on). When a limit is reached the route answers 429 with a
Retry-After header in seconds and a JSON detail. Wait that long and send the
request again; do not retry in a loop. The contract names the 429 on every route
it can come from.
| Routes | Counted per | Default |
|---|---|---|
Passkey ceremonies: POST /v1/identity/register/begin and /finish, /v1/identity/auth/begin and /finish, /v1/identity/register, /v1/identity/pairing/exchange, /v1/recovery/finish, /v1/recovery/email/verify |
client address | 600 per 5 minutes |
Tokens: POST /token, POST /revoke, POST /v1/token |
client address | 600 per 5 minutes |
Token checks: POST /v1/token/introspect, POST /v1/identity/session/verify |
client address | 600 per 5 minutes |
POST /v1/recovery/begin |
client address, and the mailbox named | 20 per 15 minutes, and 3 per 15 minutes |
POST /v1/recovery/email |
client address, the session, and the mailbox named | 200 per 5 minutes, 5 per hour, and 3 per hour |
What this means for an integration:
- A confidential client is not counted on the token routes. A request that
proves your client secret (HTTP Basic, or
client_idandclient_secretin the body) never draws on an address's budget, so a server that exchanges every user's code from one address is not limited by it. Native public clients, which hold no secret, are counted per address. - The limits on routes that send mail count the mailbox as well as the caller, so one inbox cannot be flooded from many addresses. The answer is the same for an address that has an account and one that does not. A refusal for a mailbox means links already sent to it are still valid: use the newest one.
- The numbers are sized so that a person, or a room of people behind one address,
never meets them. They bound floods and mail abuse; they are not a quota to
plan against. A deployment may set its own numbers
(
LIFECAREID_RATE_LIMIT_RULES).
Consent & revocation discipline
- Purpose binding is enforced. A token issues only against a grant whose declared purpose matches — don't reuse one purpose to back unrelated processing.
- Design for revocation. A grant can be revoked at any instant; dependent
tokens die in < 500 ms. A
403/active:falseis a normal state, not an error to retry-storm. Degrade gracefully and stop using the data. - Record accesses (data sources) so the user's ledger is a true audit trail.
- The ledger is tamper-evident (
chain_valid). If it ever readsfalse, treat it as a security incident.
Transport & storage
- TLS 1.3 in transit (LifeCare.ID runs behind the node's ingress).
- AES-256 at rest for tokens on the platform side.
- No third-party analytics in privileged surfaces (the consent panel, the identity web pages) — don't add tracking there.
- GDPR Art. 25 privacy-by-design is a prerequisite of integration; SOC 2 / BAA targets return at commercialization.
Your integration checklist
Apps
- [ ] Token exchange runs server-side; client_secret never reaches a client.
- [ ] PKCE S256 with a fresh per-attempt code_verifier; state validated.
- [ ] HTTPS redirect URIs, exact-match registered.
- [ ] Session + access tokens stored server-side, never logged or URL-borne.
- [ ] No health value in any field sent to LifeCare.ID.
- [ ] Graceful handling of revoked / expired grants and tokens.
Data sources
- [ ] Introspect every request with your own client credentials (RFC 7662 §2.1;
anonymous introspection is 401); enforce active + scope, and bind the
request to the introspected resource_owner. (For a user-self action, require a user
token whose subject_did matches the acting user instead.)
- [ ] Serve data directly to the requester — never via LifeCare.ID.
- [ ] Record each access (/v1/access/record).
- [ ] Honor revocation within targets; don't over-cache active results.
- [ ] Hold upstream provider credentials yourself, under an active grant; never
send them (or any value) to LifeCare.ID.
- [ ] Source has its own DID and signs its requests.
Node transparency — verify what you integrate with
A node publishes a public, CORS-open attestation at GET {node}/v1/attestation:
the code commit, AI models (with licenses), and parameter version it is running,
over a measurement it signs with its own key (ECDSA P-256). It carries no PHI.
LifeCare.ID hosts a public verifier, https://lifecare.id/verify,
that checks a node's attestation in the browser: it recomputes the measurement
from the published fields, verifies the signature, and looks the signing key up in
the keys LifeCare.ID lists for that node (GET /v1/node-keys, declared by the
node's operator). It works for any node: pass the address (?node=<url>) for a
one-click "verify the node you're talking to" link you can surface to your users.
- What a pass proves: the claim is internally consistent and cryptographically signed, recomputable and tamper-evident; "Verified, key known" adds that the key is the one the node's operator declared for that node. A valid signature by an unlisted key reads "Signature valid, key unknown".
- What it does not yet prove: this is the software floor, not hardware-rooted attestation; the key list is the operator's declaration today (anchoring each key in a resolvable node DID via LifeCare.ID is specified and trigger-gated). The page states this boundary plainly.
Verifying the node you integrate with is good hygiene; for high-assurance flows, treat the hardware-rooted attestation (roadmap) as the bar.
Reporting & contacts
Security issues with the platform should be reported to the network operator through the channel they provide at onboarding. 🛣️ A formal vulnerability disclosure policy and a status/changelog page are planned with the developer portal.
References
- The published, drift-tested API contract — the authoritative integration surface: openapi.json.
- This page summarizes the platform's security posture and the zero-PHI invariant; the underlying PRD and design-decision records sit alongside it in the project repository.