The Discovery flow is designed for centralized login pages where users authenticate before selecting which Organization to access.Send the OTP
Send a one-time passcode to the user’s email address:curl --request POST \
--url https://test.stytch.com/v1/b2b/otps/email/discovery/send \
--header 'Content-Type: application/json' \
--user 'PROJECT_ID:SECRET' \
--data '{
"email_address": "user@example.com"
}'
Parameters:
email_address: The user’s email address
discovery_expiration_minutes: (Optional) Expiration time in minutes (default: 10)
login_template_id: (Optional) Custom email template ID
locale: (Optional) Language for the email (e.g., “en”, “es”)
Response:{
"status_code": 200,
"request_id": "request-id-test-b05c992f-ebdc-489d-a754-c7e70ba13141"
}
Authenticate the OTP
After the user receives and enters the code, authenticate it:curl --request POST \
--url https://test.stytch.com/v1/b2b/otps/email/discovery/authenticate \
--header 'Content-Type: application/json' \
--user 'PROJECT_ID:SECRET' \
--data '{
"email_address": "user@example.com",
"code": "123456"
}'
Parameters:
email_address: The user’s email address
code: The 6-digit OTP code
Response:{
"status_code": 200,
"request_id": "request-id-test-b05c992f-ebdc-489d-a754-c7e70ba13141",
"intermediate_session_token": "SeiGwdj5lKkrEVgcEY3QNJXt6srxS3IK2Nwkar6mXD4=",
"email_address": "user@example.com",
"discovered_organizations": [
{
"organization": {
"organization_id": "organization-test-...",
"organization_name": "Acme Corp",
"organization_slug": "acme-corp"
},
"membership": {
"type": "eligible_to_join_by_email_domain",
"details": {}
}
}
]
}
Exchange the intermediate session
After the user selects an organization, exchange the intermediate session token for a full session:curl --request POST \
--url https://test.stytch.com/v1/b2b/discovery/intermediate_sessions/exchange \
--header 'Content-Type: application/json' \
--user 'PROJECT_ID:SECRET' \
--data '{
"intermediate_session_token": "SeiGwdj5lKkrEVgcEY3QNJXt6srxS3IK2Nwkar6mXD4=",
"organization_id": "organization-test-...",
"session_duration_minutes": 60
}'
Response:{
"status_code": 200,
"request_id": "request-id-test-...",
"member_id": "member-test-...",
"session_token": "mZAYn5aLEqKUlZ_Ad9U_fWr38GaAQ1oFAhT8ds245v7Q",
"session_jwt": "eyJhbGc...",
"member": { /* member object */ },
"organization": { /* organization object */ },
"session": { /* session object */ }
}
The Discovery flow returns an intermediate_session_token which must be exchanged for a full session after the user selects their organization.
The Organization flow is for organization-specific login pages where the organization context is already known (e.g., acme.yourapp.com).Send the OTP
Send a one-time passcode for a specific organization:curl --request POST \
--url https://test.stytch.com/v1/b2b/otps/email/login_or_signup \
--header 'Content-Type: application/json' \
--user 'PROJECT_ID:SECRET' \
--data '{
"organization_id": "organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
"email_address": "user@example.com"
}'
Parameters:
organization_id: The organization’s ID
email_address: The user’s email address
login_expiration_minutes: (Optional) Expiration for existing members (default: 10)
signup_expiration_minutes: (Optional) Expiration for new members (default: 10)
login_template_id: (Optional) Custom email template for login
signup_template_id: (Optional) Custom email template for signup
locale: (Optional) Language for the email
Response:{
"status_code": 200,
"request_id": "request-id-test-b05c992f-ebdc-489d-a754-c7e70ba13141",
"member_id": "member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f",
"member_created": false,
"member": { /* member object */ },
"organization": { /* organization object */ }
}
Authenticate the OTP
After the user receives and enters the code, authenticate it:curl --request POST \
--url https://test.stytch.com/v1/b2b/otps/email/authenticate \
--header 'Content-Type: application/json' \
--user 'PROJECT_ID:SECRET' \
--data '{
"organization_id": "organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
"email_address": "user@example.com",
"code": "123456",
"session_duration_minutes": 60
}'
Parameters:
organization_id: The organization’s ID
email_address: The user’s email address
code: The 6-digit OTP code
session_duration_minutes: (Optional) Session lifetime in minutes (default: 60)
session_custom_claims: (Optional) Custom claims to add to the session JWT
Response:{
"status_code": 200,
"request_id": "request-id-test-b05c992f-ebdc-489d-a754-c7e70ba13141",
"member_id": "member-test-32fc5024-9c09-4da3-bd2e-c9ce4da9375f",
"organization_id": "organization-test-07971b06-ac8b-4cdb-9c15-63b17e653931",
"session_token": "mZAYn5aLEqKUlZ_Ad9U_fWr38GaAQ1oFAhT8ds245v7Q",
"session_jwt": "eyJhbGc...",
"member_authenticated": true,
"method_id": "member-email-test-...",
"member": { /* member object */ },
"organization": { /* organization object */ },
"session": { /* session object */ }
}
If MFA is required for the Member, member_authenticated will be false and an intermediate_session_token will be returned instead. This token must be used to complete the MFA step.