Core concepts
Catalog vs credential store, two planes, three ownership levels, one resolve chain.
Five ideas explain the whole service. Everything else is detail.
1. Catalog and store are different things
The catalog is versioned YAML shipped with the service: which vendors and providers exist, what a credential for each must look like (credential_schema), what non-secret settings it takes (configuration_schema), what the provider can do, and which ownership levels it admits. It is read-only over the API and identical for everyone — see Vendors and Providers.
The credential store is PostgreSQL: encrypted secrets plus their metadata, owned by someone. The catalog says what can be configured; the store holds what actually is.
2. Two planes, two auth systems
| Plane | Paths | Who | Auth |
|---|---|---|---|
| User plane | /v1/* |
humans, product backends acting for a user | short-lived exchange JWT (User Plane Tokens) |
| Service plane | /internal/v1/* |
trusted microservices | client-credentials JWT with a scope (Service Plane Tokens) |
Both planes are served by the same deployment and domain. A user token never works on /internal/*, and a service token never works on /v1/* — different issuers, audiences, and validators. The service plane splits further by scope: provider-credentials.invoke resolves, provider-credentials.admin administers, provider-credentials.reveal reads plaintext — and none implies another.
3. Three ownership levels
Every stored credential belongs to exactly one owner:
- user — personal; owner is the
subof the caller's token (Personal Credentials). - organization — shared by a tenant; owner is the
org_idclaim; managed by the organization's manager roles (Organization Credentials). - platform — the shared fallback owned by the operator (User Plane Admin).
The catalog decides per provider which levels are allowed at all. On the user plane the owner is always taken from the token — request bodies never name an owner; only the internal admin plane addresses arbitrary owners explicitly.
4. One resolve chain
When a translation runtime asks "what key do I use for user X, provider P?", the service walks USER → ORGANIZATION → PLATFORM within the provider's policy and returns the first active default credential it finds — plaintext included, over the service plane only. There is no fallback on runtime errors of the returned key: a broken key is surfaced, never silently replaced. The whole story: Resolve Chain.
5. Secrets stay sealed
Secrets are validated against the provider's schema, encrypted (AES-256-GCM, versioned keyring), and from then on exist for the API in exactly three forms:
- masked — every read endpoint returns
masked_credentials(sk-1***abcd); enough to recognize a key, never enough to use it; - resolved — plaintext for machines, only via resolve with the
invokescope; - revealed — plaintext for a named human operator, only via reveal with its own scope, audited before the response is sent.
Deletes are soft with a recovery window (default 5 days) before physical purge; every mutation, resolve, and reveal lands in the audit trail.
Statuses gate everything
Catalog entities are active/deprecated/disabled/removed; stored credentials are active/disabled (+ soft-deleted). Deprecated providers accept no new credentials; disabled ones answer 409 on resolve. Visibility rules per role are in Vendors and Providers.