curl -X POST "${this.baseUrl}/v2/invites" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "nanoid": "te-abc123def456",
    "invites": [
      {
        "email": "newuser@company.com",
        "prefill": {
          "first_name": "John",
          "last_name": "Doe",
          "phone": "+1234567890",
          "company_name": "Acme Corp",
          "job_title": "Software Engineer",
          "company_size": "10-50",
          "company_website": "https://acme.com",
          "company_industry": "Technology",
          "company_address": "123 Main St",
          "company_city": "San Francisco",
          "company_state": "CA",
          "company_zip": "94105",
          "company_country": "USA"
        }
      }
    ],
    "role": "team_employee",
    "anonymous": false
  }'
{
  "success": true,
  "data": {
    "nanoids": ["in-abc123def456", "in-def456ghi789"]
  }
}
Rise B2B API provides a comprehensive invitation system for onboarding new users to teams and managing permissions. This guide covers the complete invite flow from creation to acceptance.

Invite Types Overview

Rise B2B API supports two distinct invite types with different flows:
Invite TypePurposeRolesFlowAuthenticationEntity Type
Regular InvitesEmployees & Contractorsteam_employee, contractorSingle POST requestNo signature requiredTeams only
Warm InvitesPre-filled Onboardingteam_employee, contractorSingle POST request with prefill dataNo signature requiredTeams only
Manager InvitesAdmin RolesTeam: team_admin, team_finance_admin, team_viewer
Company: org_admin, org_finance_admin, org_viewer
POST → Sign → PUTTyped data signature requiredTeams & Companies

Flow Comparison

Regular Invites

Simple Flow:
  1. POST /v2/invites
  2. Done!
Use for: Employees, contractors Entity: Teams only

Warm Invites

Pre-filled Flow:
  1. POST /v2/invites with prefill data
  2. Done!
Use for: Faster onboarding Entity: Teams only

Manager Invites

Typed Data Flow:
  1. POST /v2/invites/manager (get typed data)
  2. Sign typed data with wallet
  3. PUT /v2/invites/manager (execute)
Use for: Admin roles Entity: Teams & Companies

Invite Flow Overview

1

Create Invite

Generate an invitation for a new user with specific permissions
2

Send Invite

Deliver the invitation via email or direct link
3

User Accepts

User accepts the invitation and completes onboarding
4

Join Team

User is added to the team with specified permissions

Creating Invites

Regular Invites (Simple Flow)

For employees and contractors - single API call, no signature required. Only available for teams.
Warm Invites: When you provide prefill data in the invite, it becomes a “warm invite” that pre-populates the user’s onboarding form, making the process faster and more user-friendly.
Create an invitation for a new employee or contractor to join a team:
curl -X POST "${this.baseUrl}/v2/invites" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "nanoid": "te-abc123def456",
    "invites": [
      {
        "email": "newuser@company.com",
        "prefill": {
          "first_name": "John",
          "last_name": "Doe",
          "phone": "+1234567890",
          "company_name": "Acme Corp",
          "job_title": "Software Engineer",
          "company_size": "10-50",
          "company_website": "https://acme.com",
          "company_industry": "Technology",
          "company_address": "123 Main St",
          "company_city": "San Francisco",
          "company_state": "CA",
          "company_zip": "94105",
          "company_country": "USA"
        }
      }
    ],
    "role": "team_employee",
    "anonymous": false
  }'
{
  "success": true,
  "data": {
    "nanoids": ["in-abc123def456", "in-def456ghi789"]
  }
}

Manager Invites (Typed Data Flow)

For admin roles - requires typed data creation, signing, and execution. Available for both teams and companies.

Step 1: Create Typed Data

curl -X POST "${this.baseUrl}/v2/invites/manager" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "emails": ["admin@company.com", "finance@company.com"],
    "role": "org_admin",
    "nanoid": "te-abc123def456"
  }'
{
  "success": true,
  "data": {
    "invites": ["in-abc123def456", "in-def456ghi789"],
    "typed_data": {
      "domain": {
        "name": "RiseID Forwarder",
        "version": "1",
        "chainId": 137,
        "verifyingContract": "0x..."
      },
      "types": {
        "SetRoles": [
          { "name": "roles", "type": "bytes32[]" },
          { "name": "nonce", "type": "uint256" }
        ]
      },
      "value": {
        "roles": ["0x...", "0x..."],
        "nonce": 123
      }
    }
  }
}

Step 2: Sign Typed Data

const { ethers } = require('ethers');
require('dotenv').config();

async function signManagerInvite(typedData, privateKey) {
  const wallet = new ethers.Wallet(privateKey);
  const signature = await wallet.signTypedData(
    typedData.domain,
    typedData.types,
    typedData.typed_data
  );
  
  return signature;
}

Step 3: Execute Manager Invite

curl -X PUT "${this.baseUrl}/v2/invites/manager" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "invites": ["in-abc123def456", "in-def456ghi789"],
    "signer": "0x1234567890abcdef1234567890abcdef12345678",
    "typed_data": {
      "roles": ["0x...", "0x..."],
      "nonce": 123
    },
    "signature": "0x..."
  }'
{
  "success": true,
  "data": {
    "invites": ["in-abc123def456", "in-def456ghi789"],
    "transaction": "tx-abc123def456"
  }
}

Role Validation & Permissions

Regular Invites

  • Required Permission: team_admin for the team
  • Valid Roles: team_employee, contractor
  • Entity Type: Teams only

Manager Invites

  • Required Permission:
    • For teams: team_admin, org_admin
    • For companies: company, org_admin
  • Valid Roles:
    • For teams: team_admin, team_finance_admin, team_viewer
    • For companies: org_admin, org_finance_admin, org_viewer
  • Entity Type: Teams and companies
Self-Invite Prevention: You cannot invite yourself to any role. The API will reject invites where the inviter’s email matches any of the invitee emails.
const { ethers } = require('ethers');

class RiseInvites {
  constructor(baseUrl, jwtToken) {
    this.baseUrl = baseUrl;
    this.headers = {
      'Authorization': `Bearer ${jwtToken}`,
      'Content-Type': 'application/json'
    };
  }

  // ========================================
  // REGULAR INVITES (Simple Flow)
  // ========================================

  // Single API call - no signature required
  // Include prefill data for "warm invites" (pre-populated onboarding)
  async createInvite(nanoid, invites, role, anonymous = false) {
    const inviteData = {
      nanoid: nanoid,
      invites: invites,
      role: role,
      anonymous: anonymous
    };

    const response = await fetch(`${this.baseUrl}/v2/invites`, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify(inviteData)
    });

    return response.json();
  }

  // ========================================
  // MANAGER INVITES (Typed Data Flow)
  // ========================================

  // Manager invites (typed data flow)
  async createManagerInvite(emails, role, nanoid) {
    const inviteData = {
      emails: emails,
      role: role,
      nanoid: nanoid
    };

    const response = await fetch(`${this.baseUrl}/v2/invites/manager`, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify(inviteData)
    });

    return response.json();
  }

  async executeManagerInvite(invites, signer, typedData, signature) {
    const executeData = {
      invites: invites,
      signer: signer,
      typed_data: typedData,
      signature: signature
    };

    const response = await fetch(`${this.baseUrl}/v2/invites/manager`, {
      method: 'PUT',
      headers: this.headers,
      body: JSON.stringify(executeData)
    });

    return response.json();
  }

  async signManagerInvite(typedData, privateKey) {
    const wallet = new ethers.Wallet(privateKey);
    const signature = await wallet.signTypedData(
      typedData.domain,
      typedData.types,
      typedData.typed_data
    );
    
    return signature;
  }

  async getInvites(nanoid) {
    const params = new URLSearchParams({ nanoid: nanoid });
    const response = await fetch(`${this.baseUrl}/v2/invites?${params}`, {
      headers: this.headers
    });

    return response.json();
  }
}

// Usage example
async function main() {
  const invites = new RiseInvites(
    'your-base-url', // Configure for your environment
    process.env.JWT_TOKEN
  );

  try {
    // Create warm invites for employees (with prefill data)
    const regularInvites = await invites.createInvite(
      'co-abc123def456',
      [
        {
          email: 'employee@company.com',
          prefill: {
            first_name: 'John',
            last_name: 'Doe',
            phone: '+1234567890',
            company_name: 'Acme Corp',
            job_title: 'Software Engineer',
            company_size: '10-50',
            company_website: 'https://acme.com',
            company_industry: 'Technology',
            company_address: '123 Main St',
            company_city: 'San Francisco',
            company_state: 'CA',
            company_zip: '94105',
            company_country: 'USA'
          }
        }
      ],
      'team_employee',
      false
    );
    
    console.log('Regular invites created:', regularInvites.data.nanoids);

    // Create manager invites (typed data flow)
    const managerInviteData = await invites.createManagerInvite(
      ['admin@company.com', 'finance@company.com'],
      'org_admin',
      'te-abc123def456'
    );
    
    console.log('Manager invite data created:', managerInviteData.data.invites);

    // Sign the typed data
    const signature = await invites.signManagerInvite(
      managerInviteData.data.typed_data,
      process.env.PRIVATE_KEY
    );

    // Execute the manager invites
    const executedInvites = await invites.executeManagerInvite(
      managerInviteData.data.invites,
      process.env.WALLET_ADDRESS,
      managerInviteData.data.typed_data.value,
      signature
    );
    
    console.log('Manager invites executed:', executedInvites.data.transaction);

    // Get all invites
    const allInvites = await invites.getInvites('te-abc123def456');
    console.log('All invites:', allInvites.data);

  } catch (error) {
    console.error('Invite management error:', error);
  }
}

// Run the example
if (require.main === module) {
  main();
}

Managing Invites

List Team Invites

Get all invites for a team or company:
curl -X GET "${this.baseUrl}/v2/invites?nanoid=te-abc123def456" \
  -H "Authorization: Bearer YOUR_JWT_TOKEN"
{
  "success": true,
  "data": [
    {
      "nanoid": "in-abc123def456",
      "role": "team_employee",
      "invitee_name": "John Doe",
      "email": "john@company.com",
      "inviter_name": "Admin User",
      "inviter_email": "admin@company.com",
      "company": {
        "name": "Acme Corp",
        "avatar": "https://..."
      },
      "team": {
        "name": "Engineering Team",
        "avatar": "https://..."
      }
    }
  ]
}

User Acceptance Flow

Step 1: User Accesses Invite

When a user clicks the invite link, they’ll be directed to the Rise onboarding flow where they can:
  1. View invitation details - See team/company information and role
  2. Complete onboarding - Provide personal and company information
  3. Connect wallet - Link their wallet for authentication
  4. Accept invitation - Join the team with specified permissions
The onboarding process is handled through the Rise web interface at https://app.rise.works/invite/{invite_nanoid}.

Step 2: User Completes Onboarding

Users complete the onboarding process through the web interface, which includes:
  • Personal Information: Name, email, phone number
  • Company Information: Company details, job title, industry
  • Wallet Connection: Linking their wallet for authentication
  • Role Assignment: Automatic assignment based on invitation
Once completed, users are automatically added to the team with the specified role and permissions.

Invite Management Component

Error Handling

Common invite errors and solutions:
Error CodeDescriptionSolution
400nanoid parameter is requiredProvide a valid team or company nanoid
400Invalid team roleUse valid team roles: team_admin, team_finance_admin, team_viewer
400Invalid company roleUse valid company roles: org_admin, org_finance_admin, org_viewer
400You cannot invite yourselfRemove your own email from the invite list
400Invalid prefill data formatEnsure prefill data matches the required schema
400Invite not foundVerify the invite nanoids are correct
400Invalid invitesEnsure all invites are in ‘draft’ status and from the same entity
400Invite roles are differentAll invites in a batch must have the same role
400Invites are not from the same companyAll invites must be for the same team/company
403Insufficient permissionsCheck user has required permissions for the entity
404Team/Company not foundVerify the nanoid exists and is correct

Best Practices

Always verify email addresses before sending invitations to prevent spam and ensure delivery.
  • Role Assignment: Assign the minimum required role for security
  • Invite Expiration: Set appropriate expiration times (default: 30 days)
  • Personal Messages: Include personalized messages to increase acceptance rates
  • Follow-up: Send reminder emails for pending invitations
  • Permission Review: Regularly review and update invite permissions

Environment Variables

Create a .env file with your credentials:
JWT_TOKEN=your_jwt_token_here
TEAM_NANODID=your_team_nanoid_here
WALLET_ADDRESS=your_wallet_address_here
WALLET_PRIVATE_KEY=your_wallet_private_key_here

Complete Integration Example

const { ethers } = require('ethers');
require('dotenv').config();

class RiseInviteSystem {
  constructor(baseUrl = 'https://b2b-api.riseworks.io', jwtToken) {
    this.baseUrl = baseUrl;
    this.headers = {
      'Authorization': `Bearer ${jwtToken}`,
      'Content-Type': 'application/json'
    };
  }

  // Regular invites (no typed data required)
  async createInvite(nanoid, invites, role, anonymous = false) {
    const inviteData = {
      nanoid: nanoid,
      invites: invites,
      role: role,
      anonymous: anonymous
    };

    const response = await fetch(`${this.baseUrl}/v2/invites`, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify(inviteData)
    });

    return response.json();
  }

  // Manager invites (typed data flow)
  async createManagerInvite(emails, role, nanoid) {
    const inviteData = {
      emails: emails,
      role: role,
      nanoid: nanoid
    };

    const response = await fetch(`${this.baseUrl}/v2/invites/manager`, {
      method: 'POST',
      headers: this.headers,
      body: JSON.stringify(inviteData)
    });

    return response.json();
  }

  async executeManagerInvite(invites, signer, typedData, signature) {
    const executeData = {
      invites: invites,
      signer: signer,
      typed_data: typedData,
      signature: signature
    };

    const response = await fetch(`${this.baseUrl}/v2/invites/manager`, {
      method: 'PUT',
      headers: this.headers,
      body: JSON.stringify(executeData)
    });

    return response.json();
  }

  async signManagerInvite(typedData, privateKey) {
    const wallet = new ethers.Wallet(privateKey);
    const signature = await wallet.signTypedData(
      typedData.domain,
      typedData.types,
      typedData.typed_data
    );
    
    return signature;
  }

  async getInvites(nanoid) {
    const params = new URLSearchParams({ nanoid: nanoid });
    const response = await fetch(`${this.baseUrl}/v2/invites?${params}`, {
      headers: this.headers
    });

    return response.json();
  }

  async sendBulkInvites(nanoid, inviteList) {
    const results = [];
    const errors = [];

    for (const inviteData of inviteList) {
      try {
        const result = await this.createInvite(
          nanoid,
          [inviteData],
          inviteData.role,
          false
        );
        
        results.push(result);
        console.log(`Invite sent to ${inviteData.email}`);
      } catch (error) {
        errors.push({
          email: inviteData.email,
          error: error.message
        });
        console.error(`Failed to invite ${inviteData.email}:`, error.message);
      }
    }

    return { results, errors };
  }
}

// Usage example
async function main() {
  const inviteSystem = new RiseInviteSystem(
    'https://b2b-api.riseworks.io',
    process.env.JWT_TOKEN
  );

  try {
    // Regular invites for employees
    const regularInvites = await inviteSystem.createInvite(
      process.env.TEAM_NANODID,
      [
        {
          email: 'employee@company.com',
          prefill: {
            first_name: 'John',
            last_name: 'Doe',
            phone: '+1234567890',
            company_name: 'Acme Corp',
            job_title: 'Software Engineer',
            company_size: '10-50',
            company_website: 'https://acme.com',
            company_industry: 'Technology',
            company_address: '123 Main St',
            company_city: 'San Francisco',
            company_state: 'CA',
            company_zip: '94105',
            company_country: 'USA'
          }
        }
      ],
      'team_employee',
      false
    );
    
    console.log('Regular invites created:', regularInvites.data.nanoids);

    // Manager invites (typed data flow)
    const managerInviteData = await inviteSystem.createManagerInvite(
      ['admin@company.com', 'finance@company.com'],
      'org_admin',
      process.env.TEAM_NANODID
    );
    
    console.log('Manager invite data created:', managerInviteData.data.invites);

    // Sign the typed data
    const signature = await inviteSystem.signManagerInvite(
      managerInviteData.data.typed_data,
      process.env.WALLET_PRIVATE_KEY
    );

    // Execute the manager invites
    const executedInvites = await inviteSystem.executeManagerInvite(
      managerInviteData.data.invites,
      process.env.WALLET_ADDRESS,
      managerInviteData.data.typed_data.typed_data,
      signature
    );
    
    console.log('Manager invites executed:', executedInvites.data.transaction);

    // Bulk invites
    const bulkInvites = [
      {
        email: 'user1@company.com',
        prefill: {
          first_name: 'Jane',
          last_name: 'Smith',
          phone: '+1234567891',
          company_name: 'Acme Corp',
          job_title: 'Designer',
          company_size: '10-50',
          company_website: 'https://acme.com',
          company_industry: 'Technology',
          company_address: '123 Main St',
          company_city: 'San Francisco',
          company_state: 'CA',
          company_zip: '94105',
          company_country: 'USA'
        },
        role: 'team_employee'
      },
      {
        email: 'user2@company.com',
        prefill: {
          first_name: 'Bob',
          last_name: 'Johnson',
          phone: '+1234567892',
          company_name: 'Acme Corp',
          job_title: 'Analyst',
          company_size: '10-50',
          company_website: 'https://acme.com',
          company_industry: 'Technology',
          company_address: '123 Main St',
          company_city: 'San Francisco',
          company_state: 'CA',
          company_zip: '94105',
          company_country: 'USA'
        },
        role: 'team_viewer'
      }
    ];

    const bulkResult = await inviteSystem.sendBulkInvites(process.env.TEAM_NANODID, bulkInvites);
    console.log('Bulk invites completed:', bulkResult);

    // Get all invites
    const allInvites = await inviteSystem.getInvites(process.env.TEAM_NANODID);
    console.log('All invites:', allInvites.data);

  } catch (error) {
    console.error('Invite system error:', error);
  }
}

// Run the example
if (require.main === module) {
  main();
}
Need help? See the Teams page for detailed team management concepts or contact support at Hello@Riseworks.io