Skip to Content
MuoCeon Docs
v1auth contextCredential management

Credential management overview

Credential list, provider directory, delete and rename; entry points for each credential type.

Credential management is how an account manages its own ways to sign in: email, password, passkey, authenticator app, recovery codes, third-party identities. Everything here requires a session token; resource tokens are rejected — an OAuth client has no business changing how a user signs in.

Shared rules

RuleNotes
Two-step additionsThe first step (paths ending in options, or email/options) requires max_age=300 at the start of the flow; the second step (the actual write) only needs a normal session
DeleteRequires max_age=300
RenameNo step-up
ReadNo step-up
Last sign-in methodDeleting it is refused, so an account can never lock itself out

Why the second step is relaxed

The intermediate state (pending email, pending challenge, pending secret) is already bound to a userId and can be consumed once. It proves "someone just completed a verification on this session"; layering another max_age on top would only bounce the user to a fresh sign-in between two requests.

The credential object

Every credential returned by the list and the add endpoints has the same shape:

FieldNotes
idCredential id, used for delete and rename
typeemail / password / passkey / authenticator / recovery_code / oauth
displayUI label (email address, device name, provider name)
providerProvider of a third-party identity; null otherwise
labelUser-assigned label, may be null
createdCreation time
verifiedWhether it has been verified
primaryWhether it is the primary credential (currently only emails have this)
removableWhether the API allows deleting it

Endpoints

MethodPathPurposePage
GET/v1/auth/credentialsList all credentials of this accountThis page
GET/v1/auth/providersProvider directory (public, account-independent)This page
GET/v1/auth/credentials/providersProvider directory plus link stateThis page
DELETE/v1/auth/credentials/:credentialIdRemove a credentialThis page
PATCH/v1/auth/credentials/:credentialIdRenameThis page
POST/v1/auth/credentials/email/options etc.Email add / remove / set primaryEmail
PUT/v1/auth/credentials/passwordSet or change the passwordPassword
POST/v1/auth/credentials/passkey/options, /credentials/passkeyRegister a passkeyPasskey
POST/v1/auth/credentials/authenticator/options, /credentials/authenticatorBind an authenticator appAuthenticator app
POST/v1/auth/credentials/provider/:providerBind a third-party identityThird-party identity
POST/v1/auth/credentials/recovery-codesGenerate recovery codesRecovery codes

GET /credentials

Query: limit (default 20, max 100), cursor.

{
  "success": true,
  "credentials": [
    {
      "id": "cred_...",
      "type": "email",
      "display": "[email protected]",
      "provider": null,
      "label": null,
      "created": "2026-01-04T10:00:00.000Z",
      "verified": true,
      "primary": true,
      "removable": false
    }
  ],
  "next_cursor": null
}

Newest first. removable: false usually means "this is the last remaining way to sign in".

GET /providers (public)

{
  "success": true,
  "providers": [
    { "provider": "github", "display": "GitHub", "available": true }
  ]
}

The sign-in page renders these buttons before the user is signed in, so the endpoint is anonymous. It deliberately omits linked: shipping a field that is always false in a public response means the UI renders a fake value as real, which is worse than omitting it.

available means "this deployment has credentials configured for that provider", not "the code path exists".

GET /credentials/providers

Same fields as the public directory plus linked:

{
  "success": true,
  "providers": [
    { "provider": "github", "display": "GitHub", "available": true, "linked": true }
  ]
}

It pairs with the "Security settings" page, which needs to know both which providers exist and which ones are already linked.

DELETE /credentials/:credentialId

{ "success": true, "message": "Credential removed" }

Deleting writes an audit entry carrying the removed credential's id — when investigating later, the question the user needs answered is exactly "was that the one that disappeared". Unlinking a third-party identity is recorded separately as provider.unbind, because that is the remediation action when an account is being controlled from outside.

PATCH /credentials/:credentialId

ParameterTypeRequiredNotes
labelstring | null✅New label, 1–64 chars; null clears it, an empty string is rejected
{ "success": true, "credential": { "id": "cred_...", "type": "passkey" } }

Only passkeys and third-party identities carry labels; other types are rejected. For a third-party identity the label is purely local display text — renaming it does not change which account is bound.

On this page