Stytch

Stytch

Onboard clinic organizations and member access with Stytch B2B Discovery

Onboard clinic organizations and member access with Stytch B2B Discovery

Jun 9, 20264 min readBy Stytch Examples

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

typescript
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:

json
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

typescript
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:

json
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

typescript
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:

json
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

typescript
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:

json
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

typescript
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:

json
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

StytchStytch

Modern auth insights for high growth engineering teams

© 2026 Stytch. All rights reserved.