Personal credentials
CRUD on your own keys — the owner comes from the token, the secret never comes back.
Personal credentials belong to one user: the server sets owner_type=user and owner_id to the sub of your token on every call. Request bodies never name an owner, and another user's records are invisible to you — their ids answer the same 404 as nonexistent ones.
The lifecycle in six endpoints
| Step | Endpoint |
|---|---|
| Create | POST /v1/credentials |
| List / read | GET /v1/credentials · GET /v1/credentials/{id} |
| Rename / rotate | PATCH /v1/credentials/{id} |
| Default switch | POST .../make-default |
| Pause / resume | POST .../disable · POST .../enable |
| Delete | DELETE /v1/credentials/{id} |
Creation validates the secret against the provider's credential_schema, encrypts it, and stores non-secret configuration alongside. Deprecated providers refuse new records (409); the provider must also admit the user level at all (409 OWNER_TYPE_NOT_ALLOWED_FOR_PROVIDER otherwise).
Roles: user and provider_admin mutate; auditor reads only (403 on writes). All under the shared read/mutation budgets.
Masking: what reads return
Every read returns masked_credentials instead of the secret: long values keep a recognizable head/tail (sk-1***abcd), short and high-risk fields (e.g. client_secret) are masked entirely. The mask is recomputed from plaintext on every write, so after a rotation the mask changes too — a cheap way to confirm the rotation took. Configuration is returned as stored, in the open.
Plaintext exists for the API in exactly two places, neither on this plane: machine resolve and operator reveal.
Rotation is replacement
PATCH with a credentials object replaces the whole secret object — there is no per-field merge. That is deliberate: a rotation is "here is the new key", validated against the schema and re-encrypted with the active key. Optimistic locking is mandatory (version in the body or If-Match); a stale version is 409 CREDENTIAL_VERSION_CONFLICT. PATCH never touches status or is_default — those have dedicated transitions (Defaults and Lifecycle).
Worked example: cookbook scenario 03.
Deletion is soft, but immediate
DELETE removes the record from every list and from resolve at once; the ciphertext survives a recovery window (default 5 days) before physical purge, and repeating the delete is an idempotent 204. There is no undelete endpoint — recovery within the window is an operator action.