Skip to main content

Overview

Inviting users via the Wolfia API enables you to automate onboarding workflows and keep your team synchronized with your identity provider. This is especially useful when integrating with systems like Okta, Azure AD, Google Workspace, or custom HR platforms.

The user invitation endpoint

URL: POST https://wolfia.com/api/organizations/invite Authentication: API key required (see API overview for setup)

Request format

curl -X POST https://wolfia.com/api/organizations/invite \
  -H "X-API-Key: wolfia-api-YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "newuser@yourcompany.com",
    "name": "Jane Smith",
    "role": "USER"
  }'

Request parameters

ParameterTypeRequiredDescription
emailstringYesEmail address of the person to invite. Must use an approved domain for your organization.
namestringYesFull name of the person being invited
rolestringYesUser role: USER, EXPERT, or ADMIN

Understanding user roles

Wolfia uses role-based access control to manage permissions:

USER

Standard access for most team members.
  • Use Wolfia’s core features
  • Ask questions and get expert answers
  • Complete assigned questionnaires
  • Access the trust center

EXPERT

Enhanced permissions for subject matter experts.
  • Everything USER can do, plus:
  • Manage and approve knowledge base content
  • Review and validate AI-generated answers
  • Train the AI with expert knowledge

ADMIN

Full administrative access to your organization.
  • Everything EXPERT can do, plus:
  • Invite and manage users
  • Configure integrations
  • Manage organization settings
  • Create and manage API keys
Role hierarchy: You can only invite users with roles equal to or lower than your own. For example, an EXPERT can invite USER and EXPERT roles, but not ADMIN.

Response handling

Success response (200 OK)

{
  "message": "Invitation sent successfully to newuser@yourcompany.com"
}
When successful, Wolfia sends an invitation email to the user with instructions to set up their account.

Error responses

Status CodeErrorWhat it meansHow to fix
400Bad RequestEmail domain doesn’t match your organization’s approved domainsVerify the email domain is in your organization settings
401UnauthorizedInvalid or expired API keyCheck that your API key is correct and hasn’t been deactivated
403ForbiddenInsufficient permissions to invite this roleYour role doesn’t allow inviting users with the specified role
404Not FoundOrganization not foundContact support—this shouldn’t happen with a valid API key
409ConflictUser already invited or existsThis user is already part of your organization (not an error—handle gracefully)
500Server ErrorSomething went wrong on our endRetry with exponential backoff

Integration examples

Okta group synchronization

Automatically sync users from an Okta group to Wolfia:
import requests
from okta.client import Client as OktaClient
import os

WOLFIA_API_KEY = os.environ['WOLFIA_API_KEY']
OKTA_GROUP_ID = "00g1234567890abcdef"

async def sync_okta_group_to_wolfia():
    """
    Sync users from an Okta group to Wolfia.
    Run this as a scheduled job (e.g., daily cron).
    """
    okta_client = OktaClient()

    # Fetch all members from the Okta group
    group_members = await okta_client.list_group_users(OKTA_GROUP_ID)

    results = {
        'invited': [],
        'already_exists': [],
        'failed': []
    }

    for member in group_members:
        email = member.profile.email
        name = f"{member.profile.firstName} {member.profile.lastName}"

        try:
            response = requests.post(
                "https://wolfia.com/api/organizations/invite",
                headers={
                    "X-API-Key": WOLFIA_API_KEY,
                    "Content-Type": "application/json"
                },
                json={
                    "email": email,
                    "name": name,
                    "role": "USER"
                },
                timeout=10
            )

            if response.status_code == 200:
                results['invited'].append(email)
                print(f"✓ Invited {email}")
            elif response.status_code == 409:
                results['already_exists'].append(email)
                print(f"→ {email} already in organization")
            else:
                results['failed'].append({
                    'email': email,
                    'status': response.status_code,
                    'error': response.text
                })
                print(f"✗ Failed to invite {email}: {response.text}")

        except Exception as e:
            results['failed'].append({
                'email': email,
                'error': str(e)
            })
            print(f"✗ Error inviting {email}: {str(e)}")

    # Summary
    print(f"\n📊 Summary:")
    print(f"   Invited: {len(results['invited'])}")
    print(f"   Already exists: {len(results['already_exists'])}")
    print(f"   Failed: {len(results['failed'])}")

    return results

# Run the sync
if __name__ == "__main__":
    import asyncio
    asyncio.run(sync_okta_group_to_wolfia())

Azure AD automation

Sync users based on Azure AD group membership:
const axios = require('axios');
const { Client } = require('@microsoft/microsoft-graph-client');

const WOLFIA_API_KEY = process.env.WOLFIA_API_KEY;
const AZURE_GROUP_ID = 'your-azure-group-id';

async function syncAzureGroupToWolfia() {
  // Initialize Microsoft Graph client
  const graphClient = Client.initWithMiddleware({
    authProvider: /* your auth provider */
  });

  // Get all members from the Azure AD group
  const response = await graphClient
    .api(`/groups/${AZURE_GROUP_ID}/members`)
    .get();

  const results = {
    invited: [],
    alreadyExists: [],
    failed: []
  };

  for (const member of response.value) {
    const email = member.mail || member.userPrincipalName;
    const name = member.displayName;

    try {
      const apiResponse = await axios.post(
        'https://wolfia.com/api/organizations/invite',
        {
          email: email,
          name: name,
          role: 'USER'
        },
        {
          headers: {
            'X-API-Key': WOLFIA_API_KEY,
            'Content-Type': 'application/json'
          },
          timeout: 10000
        }
      );

      results.invited.push(email);
      console.log(`✓ Invited ${name}`);

    } catch (error) {
      if (error.response?.status === 409) {
        results.alreadyExists.push(email);
        console.log(`→ ${name} already exists`);
      } else {
        results.failed.push({
          email,
          error: error.message
        });
        console.error(`✗ Failed to invite ${name}:`, error.message);
      }
    }
  }

  // Print summary
  console.log('\n📊 Summary:');
  console.log(`   Invited: ${results.invited.length}`);
  console.log(`   Already exists: ${results.alreadyExists.length}`);
  console.log(`   Failed: ${results.failed.length}`);

  return results;
}

// Run the sync
syncAzureGroupToWolfia()
  .then(results => {
    console.log('\n✅ Sync completed');
    process.exit(0);
  })
  .catch(error => {
    console.error('❌ Sync failed:', error);
    process.exit(1);
  });

Bulk CSV import

Invite multiple users from a CSV file:
import requests
import csv
import os
from time import sleep

WOLFIA_API_KEY = os.environ['WOLFIA_API_KEY']
CSV_FILE_PATH = 'users_to_invite.csv'

def bulk_invite_from_csv(csv_path):
    """
    Bulk invite users from a CSV file.
    CSV format: email,name,role
    Example: jane.doe@company.com,Jane Doe,USER
    """
    results = {
        'invited': [],
        'already_exists': [],
        'failed': []
    }

    with open(csv_path, 'r') as csvfile:
        reader = csv.DictReader(csvfile)

        for row in reader:
            email = row['email'].strip()
            name = row['name'].strip()
            role = row.get('role', 'USER').strip().upper()

            # Validate role
            if role not in ['USER', 'EXPERT', 'ADMIN']:
                print(f"⚠️  Invalid role '{role}' for {email}, using USER instead")
                role = 'USER'

            try:
                response = requests.post(
                    'https://wolfia.com/api/organizations/invite',
                    headers={
                        'X-API-Key': WOLFIA_API_KEY,
                        'Content-Type': 'application/json'
                    },
                    json={
                        'email': email,
                        'name': name,
                        'role': role
                    },
                    timeout=10
                )

                if response.status_code == 200:
                    results['invited'].append(email)
                    print(f"✓ Invited {name} ({email}) as {role}")
                elif response.status_code == 409:
                    results['already_exists'].append(email)
                    print(f"→ {email} already exists")
                else:
                    results['failed'].append({
                        'email': email,
                        'status': response.status_code,
                        'error': response.text
                    })
                    print(f"✗ Failed to invite {email}: {response.text}")

                # Rate limiting: be nice to the API
                sleep(0.1)

            except Exception as e:
                results['failed'].append({
                    'email': email,
                    'error': str(e)
                })
                print(f"✗ Error inviting {email}: {str(e)}")

    # Print summary
    print(f"\n📊 Summary:")
    print(f"   Total processed: {len(results['invited']) + len(results['already_exists']) + len(results['failed'])}")
    print(f"   ✓ Invited: {len(results['invited'])}")
    print(f"   → Already exists: {len(results['already_exists'])}")
    print(f"   ✗ Failed: {len(results['failed'])}")

    # Save failed invites for retry
    if results['failed']:
        with open('failed_invites.csv', 'w', newline='') as f:
            writer = csv.DictWriter(f, fieldnames=['email', 'error'])
            writer.writeheader()
            writer.writerows(results['failed'])
        print(f"\n💾 Failed invitations saved to failed_invites.csv")

    return results

if __name__ == '__main__':
    bulk_invite_from_csv(CSV_FILE_PATH)
Example CSV format:
email,name,role
jane.smith@company.com,Jane Smith,USER
john.doe@company.com,John Doe,EXPERT
admin@company.com,Sarah Admin,ADMIN

Best practices

Email domain validation

Before sending invitations, verify that email domains match your organization’s approved domains:
  • View approved domains in your organization settings
  • Pre-validate emails in your integration code to avoid 400 errors
  • Contact support if you need to add new domains

Error handling strategies

A 409 status means the user already exists or has been invited. This is normal and shouldn’t be treated as an error.
if response.status_code == 409:
    print(f"User {email} already in organization")
    # Continue processing other users
    continue
Use exponential backoff for 5xx errors:
import time

max_retries = 3
for attempt in range(max_retries):
    try:
        response = requests.post(...)
        if response.status_code < 500:
            break
    except Exception:
        if attempt < max_retries - 1:
            wait_time = 2 ** attempt  # 1s, 2s, 4s
            time.sleep(wait_time)
        else:
            raise
Keep detailed logs of failed invitations:
import logging

logging.basicConfig(
    filename='wolfia_invites.log',
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)

if response.status_code != 200:
    logging.error(
        f"Failed to invite {email}: "
        f"status={response.status_code}, "
        f"response={response.text}"
    )
Track your integration’s health:
  • Log successful invitations
  • Monitor error rates
  • Set up alerts for unusual patterns
  • Review the “Last Used” timestamp in your API settings

Role mapping strategies

When syncing from identity providers, map their roles to Wolfia roles appropriately:
# Example: Map Okta roles to Wolfia roles
ROLE_MAPPING = {
    'Security Admin': 'ADMIN',
    'Security Team': 'EXPERT',
    'Engineering': 'USER',
    'Sales': 'USER'
}

okta_role = member.profile.customAttribute.get('department')
wolfia_role = ROLE_MAPPING.get(okta_role, 'USER')  # Default to USER

Scheduled automation

Set up automated syncs using your preferred scheduler:

Linux/Mac (cron)

# Run daily at 2 AM
0 2 * * * /usr/bin/python3 /path/to/sync_users.py >> /var/log/wolfia_sync.log 2>&1

Windows (Task Scheduler)

  1. Open Task Scheduler
  2. Create a new task
  3. Set trigger (e.g., daily at 2 AM)
  4. Set action: Run Python script
  5. Configure logging and error handling

Cloud platforms

  • AWS Lambda: Schedule with EventBridge
  • Azure Functions: Use Timer trigger
  • Google Cloud Functions: Use Cloud Scheduler

Monitoring and alerts

Track your integration’s performance:
  • Success rate: Monitor the ratio of successful to failed invitations
  • API errors: Alert on 5xx errors or high failure rates
  • Domain mismatches: Track 400 errors to identify configuration issues
  • Duplicate attempts: Monitor 409 responses—high rates might indicate sync logic issues

Getting help

If you encounter issues inviting users via the API:
  • Check email domains: Verify invited emails use approved domains in organization settings
  • Verify API key: Confirm your key is active in API settings
  • Review error logs: Check the full error response for specific guidance
  • Contact support: Email support@wolfia.com with your integration details

Next steps

⌘I