Healthcare SaaS platforms need to rapidly onboard clinic organizations and provision member access without manual admin overhead or complex signup flows. Stytch provides passwordless B2B Discovery APIs that automate tenant creation and member provisioning through secure magic link authentication. Stytch's B2B Discovery flow enables clinics to self-service signup, create their organization, and invite team members in seconds without passwords. By automating organization provisioning and member access delegation, you eliminate manual onboarding work and reduce time-to-value for new tenants.
What this tutorial covers
- •Outcome: You will implement a complete B2B clinic onboarding flow that creates organizations, authenticates new members, and provisions access via Stytch magic links.
- •Endpoints used: `POST /v1/b2b/magic_links/email/discovery/send`, `POST /v1/b2b/magic_links/discovery/authenticate`, `POST /v1/b2b/discovery/organizations/create`, `POST /v1/b2b/discovery/intermediate_sessions/exchange`, `POST /v1/b2b/magic_links/email/invite`
- •Language: typescript
- •Auth: API key (Authorization header with Bearer token)
- •Estimated implementation time: ~18 minutes
Step 1: Send discovery magic link to new clinic contact via Stytch
Start the onboarding journey by sending a discovery email to a clinic representative. Stytch's discovery endpoint initiates organization identification without requiring pre-existing tenant records.
Initiate discovery email flow
1import * as stytch from 'stytch';
2
3const client = new stytch.B2BClient({
4 project_id: process.env.STYTCH_PROJECT_ID!,
5 secret: process.env.STYTCH_SECRET!,
6});
7
8const clinicContactEmail = 'contact@clinic.com';
9
10const discoveryResponse = await client.magicLinks.email.discovery.send({
11 email_address: clinicContactEmail,
12});
13
14console.log('Discovery magic link sent to clinic contact:', discoveryResponse);Response:
1const stytch = require('stytch');
2
3const client = new stytch.B2BClient({
4 project_id: '${projectId}',
5 secret: '${secret}',
6});
7
8const params = {
9 email_address: "${email}",
10};
11
12client.magicLinks.email.discovery.send(params)
13 .then(resp => { console.log(resp) })
14 .catch(err => { console.log(err) });Step 2: Authenticate the discovery link and obtain an intermediate session
When the clinic contact clicks the magic link, exchange the token for an intermediate session. This session grants temporary access to organization discovery and creation without a permanent account.
Validate discovery token and create session
1const stytch = require('stytch');
2
3const client = new stytch.B2BClient({
4 project_id: '${projectId}',
5 secret: '${secret}',
6});
7
8const discoveryToken = '${token}';
9const params = {
10 discovery_magic_links_token: discoveryToken,
11};
12
13const resp = await client.magicLinks.discovery.authenticate(params);
14const intermediateSessionToken = resp.intermediate_session_token;Response:
1const stytch = require('stytch');
2
3const client = new stytch.B2BClient({
4 project_id: '${projectId}',
5 secret: '${secret}',
6});
7
8const params = {
9 discovery_magic_links_token: "${token}",
10};
11
12client.magicLinks.discovery.authenticate(params)
13 .then(resp => { console.log(resp) })
14 .catch(err => { console.log(err) });Step 3: Create a new clinic organization within the discovery session
If the clinic contact does not belong to an existing organization, use the intermediate session to provision a new tenant record. Stytch automatically captures organization metadata needed for multi-tenant isolation.
Provision new organization from discovery
1const stytch = require('stytch');
2
3const client = new stytch.B2BClient({
4 project_id: '${projectId}',
5 secret: '${secret}',
6});
7
8const params = {
9 intermediate_session_token: "${token}",
10 organization_name: "${organizationName}",
11 organization_slug: "${organizationSlug}",
12 organization_external_id: "my-external-id",
13};
14
15client.discovery.organizations.create(params)
16 .then(resp => { console.log(resp) })
17 .catch(err => { console.log(err) });Response:
1const stytch = require('stytch');
2
3const client = new stytch.B2BClient({
4 project_id: '${projectId}',
5 secret: '${secret}',
6});
7
8const params = {
9 intermediate_session_token: "${token}",
10 organization_name: "${organizationName}",
11 organization_slug: "${organizationSlug}",
12 organization_external_id: "my-external-id",
13};
14
15const resp = await client.discovery.organizations.create(params);
16console.log(resp);Step 4: Exchange intermediate session for permanent member access with Stytch
Convert the intermediate session into a permanent member session tied to the newly created organization. Stytch issues a session token that authenticates the clinic admin and grants organization-level permissions.
Finalize member provisioning and session
1const stytch = require('stytch');
2
3const client = new stytch.B2BClient({
4 project_id: '${projectId}',
5 secret: '${secret}',
6});
7
8const intermediateSessionToken = "${token}";
9const organizationId = "${organizationId}";
10
11const params = {
12 intermediate_session_token: intermediateSessionToken,
13 organization_id: organizationId,
14};
15
16const resp = await client.discovery.intermediateSessions.exchange(params);
17console.log(resp);Response:
1const stytch = require('stytch');
2
3const client = new stytch.B2BClient({
4 project_id: '${projectId}',
5 secret: '${secret}',
6});
7
8const params = {
9 intermediate_session_token: "${token}",
10 organization_id: "${organizationId}",
11};
12
13client.discovery.intermediateSessions.exchange(params)
14 .then(resp => { console.log(resp) })
15 .catch(err => { console.log(err) });Step 5: Invite additional clinic team members via Stytch member invites
Once the clinic admin is provisioned, send magic link invitations to additional team members. Stytch's invite endpoint accelerates member onboarding without requiring the admin to manually manage user creation.
Send member invitation emails
1import stytch from 'stytch';
2
3const client = new stytch.B2BClient({
4 project_id: process.env.STYTCH_PROJECT_ID,
5 secret: process.env.STYTCH_SECRET,
6});
7
8const teamMembers = ['doctor@clinic.com', 'nurse@clinic.com', 'admin@clinic.com'];
9const organizationId = process.env.CLINIC_ORG_ID;
10const sessionToken = process.env.ADMIN_SESSION_TOKEN;
11
12for (const email of teamMembers) {
13 const params = {
14 organization_id: organizationId,
15 email_address: email,
16 };
17
18 const options = {
19 authorization: {
20 session_token: sessionToken,
21 },
22 };
23
24 const resp = await client.magicLinks.email.invite(params, options);
25 console.log(`Invitation sent to ${email}:`, resp);
26}Response:
1const stytch = require('stytch');
2
3const client = new stytch.B2BClient({
4 project_id: '${projectId}',
5 secret: '${secret}',
6});
7
8const params = {
9 organization_id: "${organizationId}",
10 email_address: "${email}",
11};
12
13const options = {
14 authorization: {
15 session_token: '${sessionToken}',
16 },
17};
18
19client.magicLinks.email.invite(params, options)
20 .then(resp => { console.log(resp) })
21 .catch(err => { console.log(err) });Common pitfalls when using Stytch
- •Intermediate session expiry. Discovery intermediate sessions are short-lived. If the clinic contact does not complete organization creation and member provisioning within the session window, they must restart the discovery flow. Implement clear UX signals around session state.
- •Missing organization metadata. Clinic-specific metadata (clinic type, specialization, EHR integration flags) should be captured during organization creation. Omitting this data forces retroactive updates and complicates downstream provisioning logic.
- •Role delegation and audit. When inviting team members, ensure your application tracks role assignments and invitation acceptance. Stytch provides invitation tokens but does not store clinic-specific role semantics; your control plane must validate role hierarchy and log admin delegations.
- •Email deliverability and DNS. Discovery and invite emails are transactional. Configure your domain SPF, DKIM, and DMARC records to avoid Stytch magic links landing in spam, especially critical for healthcare organizations with strict email security policies.
Ready to accelerate clinic onboarding? Integrate Stytch's B2B Discovery and member invite APIs to eliminate manual tenant provisioning and ship self-service signup in days, not weeks.
Documentation references
The code examples in this tutorial are grounded in the following docs pages:
- •
- •
- •
- •
- •
Build modern authentication faster with Stytch
Join leading teams using Stytch APIs to ship secure auth flows, reduce friction, and strengthen your product’s security.
Read More Blog Posts
Build a marketplace with OAuth connected apps and secret rotation
Managing third-party OAuth integrations and rotating secrets securely in a B2B marketplace is complex and error-prone without proper controls. Stytch provides A
Stytch vs WorkOS: Building Enterprise Agent Workflows with Modern Auth
Engineering teams at B2B SaaS companies must choose between Stytch and WorkOS to build secure, scalable authentication for agent-driven workflows that demand fi

