> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# Sign-in methods

> Configure password, email code, magic link, phone code, passkey, social, organization SSO and Didit ID Wallet sign-in, plus MFA and password reset.

> [!WARNING]
>
> Authentication is new. The hosted sign-in page runs every method on this page. The JSON endpoints listed here are for teams that build their own sign-in screens (`sign_in_ui: "own"`).

Enable a method with `PUT methods/{name}/` and `{"enabled": true}` on your application's Authentication management API, or from the console. Hosted sign-in shows only the methods you enabled.

| Method | Name | Setup |
| - | - | - |
| Password | `password` | Also needed for email sign-up with a password. |
| Email code | `code` | None. |
| Magic link | `magic` | None. Hosted pages only. |
| Phone code | `phone` | Send the `workflow_id` of a phone verification workflow. |
| Passkey | `passkey` | None. |
| Social sign-in | `google`, `microsoft`, `apple`, `github` | Your own `client_id` and `client_secret`. Sandbox applications can use Didit's test apps instead (`credential_source: "didit_test"`). |
| Organization SSO | `sso` | A SAML or OIDC connection per customer organization. |
| Didit ID Wallet | `wallet` | Always on. It cannot be turned off. |

Each JSON call below starts with a `transaction` from `POST authorize/`. That transaction lasts 10 minutes. A successful sign-in returns `{code, redirect_uri, state}`. Exchange the code at `POST token/` within 60 seconds (see [tokens and JWKS](/authentication/tokens-and-jwks.md)).

Instead of a code, a sign-in call can return one of these steps. Finish the step, then you receive the code.

| Response | Next step |
| - | - |
| `mfa_required` with `challenge` and `factors` | Send a TOTP or recovery code, or use a passkey (see [MFA](#mfa)). |
| `mfa_enrollment_required` with `challenge` and `uri` | The MFA policy requires an authenticator. Enroll it with `POST mfa/setup/`. |
| `protection_required` with `challenge` and `methods` | A [bot protection](/authentication/bot-protection.md) policy asks for an extra proof. |
| `verification_required` with `verification_url` | Your verification settings require a liveness or ID check at sign-up or sign-in. Send the person to `verification_url` (valid 30 minutes); the code follows an approved check. |

## Password

`POST password/` takes `transaction`, `email` and `password`.

* A password must meet your `password_min_length` (8, 10, 12 or 14 characters; 10 by default). Common and all-numeric passwords are rejected with `password_policy`. When the [breached-password check](/authentication/bot-protection.md#breached-passwords) is on, a known breached password is rejected with `password_breached`.
* Only people with a verified email can sign in with a password.
* A wrong password, an unknown email, an unverified email, a locked account and a blocked account all get the same answer: `400 {"error": "invalid_credentials"}`. Your UI cannot tell which one it was, and neither can an attacker.

### Sign-up

Sign-up verifies the email first. Call `POST email/start/` with `purpose: "signup"`, `email` and `password`, then `POST email/complete/` with the code. The account is created with a verified email.

* `signup_policy` decides who can sign up: `anyone`, `invited` (only with an invitation link) or `allowed_domains`.
* With `allow_subaddresses: false` (the default), an address with `+` in the local part is rejected.
* When your branding sets terms or privacy links, the hosted sign-up form asks the person to accept them.

## Email code

`POST email/start/` takes `transaction`, `purpose` (`login` or `signup`) and `email`, and returns `202 {challenge, expires_in: 600}`. The person receives a 6-digit code that lasts 10 minutes.

`POST email/complete/` takes `transaction`, `purpose`, `challenge` and `code`. You get 5 attempts, then the person must request a new code. The hosted page offers a new code after 30 seconds.

A login code for an address with no account creates one when your `signup_policy` allows it. This is passwordless sign-up.

## Magic link

The hosted page sends a sign-in link that lasts 15 minutes. It stays tied to the original PKCE transaction, so the link works only in the flow that started it. The person confirms the sign-in on the page the link opens. There is no JSON endpoint for magic links.

## Phone code

`POST phone/start/` takes `transaction`, `phone` and an optional `channel` (`sms`, the default, or `whatsapp`). The number is normalized to E.164. It returns `202 {challenge, expires_in: 600}`.

`POST phone/complete/` takes `transaction`, `challenge` and the 6-digit `code`. You get 5 attempts.

* Phone codes are delivered through Didit's phone verification. Enable the method with the `workflow_id` of a workflow that contains phone verification.
* A first phone sign-in creates an account only when `signup_policy` is `anyone`.
* If delivery fails, the call returns `503 {"error": "delivery_unavailable"}`.

## Passkeys

Passkeys use WebAuthn. The relying party is your application's sign-in origin. Didit asks for a discoverable credential (resident key) and always requires user verification, such as a fingerprint, face or device PIN. Didit does not ask for attestation.

| Call | Body | Result |
| - | - | - |
| `POST passkeys/begin/` | `{transaction}` | `{challenge, options}` for `navigator.credentials.get()`. No credential list is sent, so the browser offers every passkey for your origin. |
| `POST passkeys/complete/` | `{challenge, credential}` | A sign-in code, or a next step. |
| `POST passkeys/register/` | Empty, with `Authorization: Bearer <access_token>` | Options for `navigator.credentials.create()`. |
| `POST passkeys/confirm/` | `{challenge, credential}`, with the same token | `{id}` of the new passkey. |

* A passkey challenge lasts 5 minutes.
* Adding a passkey needs a session created within the last 5 minutes. Otherwise the call returns `403 {"error": "reauthentication_required"}`. Sign the person in again first.
* A passkey sign-in counts as multi-factor.
* After sign-up, the hosted page offers to add a passkey (`passkeys/setup/begin/` and `passkeys/setup/complete/`).
* Android apps registered on the application can use the same passkeys.

## Social sign-in

Google, Microsoft, Apple and GitHub sign-in use your own OAuth app credentials, set with `PUT methods/{provider}/` and `credentials: {client_id, client_secret}`. The provider callback is on your sign-in origin.

The first time someone signs in with a provider, they also prove they own the email address with a code (`POST social/email/start/` and `POST social/email/verify/`). Later sign-ins with that provider skip this step.

GitHub cannot prove a recent sign-in, so it is not offered when you send `prompt=login` or `max_age`.

## Organization SSO

Organization SSO lets a customer's company sign in through its own SAML or OIDC identity provider. Configure it per customer organization at `organizations/{id}/sso/` with `protocol` (`saml` or `oidc`), `enabled` and `config`.

* You can send the customer's IT team a setup link that lasts 7 days.
* Your SAML service provider metadata is published at `{origin}/sso/saml/metadata/`.
* Hosted sign-in finds the connection from the person's verified email domain, or from `organization=<slug>` on the sign-in URL.

## Didit ID Wallet

People who already verified with Didit can sign in with their Didit ID Wallet. This method is always on. The session's `verified_identity` then carries what the person chose to share.

## MFA

The second factor is an authenticator app (TOTP) with recovery codes, or a passkey.

* `mfa_policy` decides who must use MFA: `optional`, `everyone` or `organization_admins` (the default). Anyone who has enrolled an authenticator must always use it.
* To enroll an authenticator, call `POST mfa/enroll/` with a fresh session (created within 5 minutes). It returns `{challenge, uri}`; show the `uri` as a QR code. Then send the first code to `POST mfa/confirm/`. The response holds 10 single-use recovery codes. Show them once.
* At sign-in, send a TOTP or recovery code to `POST mfa/complete/` with the `challenge`. You get 5 attempts. To use a passkey instead, call `POST mfa/passkey/begin/` and `POST mfa/passkey/complete/`.
* Codes use 30-second steps. A code that was already accepted cannot be used again.
* SMS and email codes are not available as second factors.

## Forgot and reset password

`POST password-reset/start/` takes `email` and returns `202 {"status": "sent", "expires_in": 1800}`. The answer is the same whether or not the address has an account. The reset link lasts 30 minutes.

`POST password-reset/complete/` takes `token` and `password` and returns `{"status": "password_changed"}`. It also:

* clears a lockout,
* ends every session when `revoke_on_password_change` is on (the default),
* invalidates other pending codes and links for that person.

## Invitations

With `signup_policy: "invited"`, only invited people can sign up. An invitation link lasts 7 days and opens the hosted `/invite` page.

Continue with [tokens and JWKS](/authentication/tokens-and-jwks.md), or read [bot protection](/authentication/bot-protection.md) for lockouts and challenges.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.