Delete Credential
Delete credential by ID
Authorization
bearerAuth In: header
Path Parameters
Response Body
application/json
application/json
application/json
application/json
application/json
application/json
application/json
application/json
curl -X DELETE "https://example.com/v1/users/credentials/497f6eca-6276-4993-bfeb-53cbbbba6f08"{
"status": 200,
"message": "OK"
}{
"status": 400,
"message": "Invalid user input",
"code": "users_m.invalid_user_input",
"class": "validation"
}{
"status": 401,
"message": "Unauthorized",
"code": "common.unauthorized",
"class": "business"
}{
"status": 403,
"message": "No access to the record",
"code": "common.rbac_no_rec_access",
"class": "business"
}{
"status": 404,
"message": "User not found",
"code": "common.user_not_found",
"class": "business"
}{
"status": 429,
"message": "Rate limit for 203.0.113.10 to 456e1234-e89b-12d3-a456-426614174111 exceeded.",
"code": "rate_limits_m.exceeded",
"class": "temporary",
"retryable": true
}{
"status": 500,
"message": "Internal server error",
"code": "common.server_error",
"class": "business"
}{
"overall_status": "unhealthy",
"message": "Service is shutting down",
"timestamp": "2026-08-27T15:04:05Z"
}Update an existing credential. A `value` already confirmed on another account is stored like any other, unvalidated, and answered like any other — no code that could confirm it is sent, and the account that holds it is notified that an attempt was made. A `value` the caller already holds on another credential of their own answers `409 users_m.duplicate_credential`. Uniqueness counts confirmed credentials only; surrounding whitespace is trimmed before the value is compared and stored, and the comparison is case-insensitive for every credential type. A `value` that is empty after trimming is treated as absent: the credential is left untouched and no code is issued. Re-submitting the value the credential already holds is not a change and is not checked, but it still un-validates the credential and re-issues a code. Changing `value` re-issues a one-time code, so the OTP refusals reach this path: `429 otp_m.cooling_period_active`, `500 otp_m.failed_send_otp`, `500 otp_m.cache_error`. Send pacing answers success with `code_sent: false` and a `retry_after`; only where no code is left does it answer `429 otp_m.resend_too_soon` or `429 otp_m.resend_limit_exceeded`. A `value` that cannot be delivered to is refused with `400` before the row is written: `users_m.invalid_phone` for a phone number that does not parse — one without a country code, for instance — and `users_m.invalid_email`, `users_m.invalid_email_spaces` or `users_m.invalid_email_length` for an e-mail. Credential types this module does not shape-check are unaffected.
AUTHENTICATED BY X-API-Key, not by a bearer token. auth.APIKeyMiddleware REPLACES the usual chain on this route, so there is no user context, no RBAC check and no licence check — and the refusals are common.api_key_required / common.invalid_api_key rather than common.unauthorized / common.rbac_no_rec_access. An OPTIONS request skips the key check entirely. Request a password reset link for an email or phone credential. Subject to IP rate limiting (max attempts per 15 minutes; failed attempts tracked per hour). Always returns success when the credential is unknown to prevent enumeration.