Skip to main content
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. 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). Instead of a code, a sign-in call can return one of these steps. Finish the step, then you receive the code.

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 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. 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.
  • 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, or read bot protection for lockouts and challenges.