Skip to main content
POST
/
admin
/
invite
curl -X POST "http://localhost:8080/admin/invite" \
  -H "Authorization: Bearer service_role_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "newuser@example.com",
    "data": {
      "first_name": "John",
      "last_name": "Doe",
      "role": "team_member"
    },
    "redirect_to": "https://yourapp.com/welcome"
  }'
{
  "user": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "aud": "authenticated",
    "role": "authenticated",
    "email": "newuser@example.com",
    "phone": null,
    "email_confirmed_at": null,
    "phone_confirmed_at": null,
    "invited_at": "2023-01-01T00:00:00Z",
    "confirmation_sent_at": "2023-01-01T00:00:00Z",
    "app_metadata": {
      "provider": "email",
      "providers": ["email"]
    },
    "user_metadata": {
      "first_name": "John",
      "last_name": "Doe",
      "role": "team_member"
    },
    "created_at": "2023-01-01T00:00:00Z",
    "updated_at": "2023-01-01T00:00:00Z"
  },
  "message_id": "msg_1234567890abcdef"
}

Documentation Index

Fetch the complete documentation index at: https://docs.strikebet.app/llms.txt

Use this file to discover all available pages before exploring further.

Send user invitations via email with customizable invitation links and user data. This endpoint allows administrators to invite new users to join the platform with pre-configured roles and metadata.
This endpoint requires service role authentication. Only use the service role key on your backend servers, never in client-side code.
curl -X POST "http://localhost:8080/admin/invite" \
  -H "Authorization: Bearer service_role_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "email": "newuser@example.com",
    "data": {
      "first_name": "John",
      "last_name": "Doe",
      "role": "team_member"
    },
    "redirect_to": "https://yourapp.com/welcome"
  }'

Request Body

email
string
required
Email address to send the invitation to.
data
object
User metadata to pre-populate when the invitation is accepted.Common fields:
  • first_name - User’s first name
  • last_name - User’s last name
  • role - User’s role in the system
  • team_id - Team or organization ID
  • department - User’s department
redirect_to
string
URL to redirect to after the invitation is accepted. If not provided, uses the default redirect URL.
options
object
Additional invitation options.Available options:
  • captcha_token - Captcha token for verification
  • data - Additional app metadata

Response

user
object
Created user object with invitation status
message_id
string
Unique identifier for the sent invitation email
{
  "user": {
    "id": "123e4567-e89b-12d3-a456-426614174000",
    "aud": "authenticated",
    "role": "authenticated",
    "email": "newuser@example.com",
    "phone": null,
    "email_confirmed_at": null,
    "phone_confirmed_at": null,
    "invited_at": "2023-01-01T00:00:00Z",
    "confirmation_sent_at": "2023-01-01T00:00:00Z",
    "app_metadata": {
      "provider": "email",
      "providers": ["email"]
    },
    "user_metadata": {
      "first_name": "John",
      "last_name": "Doe",
      "role": "team_member"
    },
    "created_at": "2023-01-01T00:00:00Z",
    "updated_at": "2023-01-01T00:00:00Z"
  },
  "message_id": "msg_1234567890abcdef"
}

Error Responses

{
  "code": 400,
  "msg": "Invalid email format",
  "details": "Please provide a valid email address"
}

Invitation Email Template

The invitation email includes:
<!DOCTYPE html>
<html>
<head>
  <title>You're Invited to Join Strike</title>
</head>
<body>
  <div style="max-width: 600px; margin: 0 auto; padding: 20px;">
    <h1>You're Invited!</h1>
    
    <p>Hi {{.FirstName}},</p>
    
    <p>You've been invited to join Strike as a {{.Role}}.</p>
    
    <a href="{{.InvitationURL}}" 
       style="background-color: #0D9373; color: white; padding: 12px 24px; text-decoration: none; border-radius: 4px; display: inline-block;">
      Accept Invitation
    </a>
    
    <p>Or copy and paste this link into your browser:</p>
    <p>{{.InvitationURL}}</p>
    
    <p>This invitation will expire in 7 days.</p>
    
    <p>Welcome to the team!</p>
    
    <p>Best regards,<br>The Strike Team</p>
  </div>
</body>
</html>

Implementation Examples

React Admin Invitation Form

import { useState } from 'react';

function AdminInviteForm({ onInviteSent }) {
  const [formData, setFormData] = useState({
    email: '',
    firstName: '',
    lastName: '',
    role: 'user',
    teamId: '',
    department: ''
  });
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const [success, setSuccess] = useState(false);

  const handleSubmit = async (e) => {
    e.preventDefault();
    setLoading(true);
    setError('');
    setSuccess(false);

    try {
      const response = await fetch('/api/admin/invite', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${process.env.REACT_APP_SERVICE_ROLE_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          email: formData.email,
          data: {
            first_name: formData.firstName,
            last_name: formData.lastName,
            role: formData.role,
            team_id: formData.teamId,
            department: formData.department
          },
          redirect_to: 'https://yourapp.com/welcome'
        }),
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.msg || 'Failed to send invitation');
      }

      const result = await response.json();
      setSuccess(true);
      onInviteSent?.(result);
      
      // Reset form
      setFormData({
        email: '',
        firstName: '',
        lastName: '',
        role: 'user',
        teamId: '',
        department: ''
      });
      
    } catch (err) {
      setError(err.message);
    } finally {
      setLoading(false);
    }
  };

  const handleInputChange = (field, value) => {
    setFormData(prev => ({
      ...prev,
      [field]: value
    }));
  };

  return (
    <div className="admin-invite-form">
      <h2>Invite New User</h2>
      
      {error && (
        <div className="error-message">
          {error}
        </div>
      )}

      {success && (
        <div className="success-message">
          ✅ Invitation sent successfully!
        </div>
      )}

      <form onSubmit={handleSubmit} className="invite-form">
        <div className="form-row">
          <div className="form-group">
            <label htmlFor="firstName">First Name</label>
            <input
              type="text"
              id="firstName"
              value={formData.firstName}
              onChange={(e) => handleInputChange('firstName', e.target.value)}
              required
            />
          </div>
          
          <div className="form-group">
            <label htmlFor="lastName">Last Name</label>
            <input
              type="text"
              id="lastName"
              value={formData.lastName}
              onChange={(e) => handleInputChange('lastName', e.target.value)}
              required
            />
          </div>
        </div>

        <div className="form-group">
          <label htmlFor="email">Email Address</label>
          <input
            type="email"
            id="email"
            value={formData.email}
            onChange={(e) => handleInputChange('email', e.target.value)}
            placeholder="user@example.com"
            required
          />
        </div>

        <div className="form-row">
          <div className="form-group">
            <label htmlFor="role">Role</label>
            <select
              id="role"
              value={formData.role}
              onChange={(e) => handleInputChange('role', e.target.value)}
            >
              <option value="user">User</option>
              <option value="team_member">Team Member</option>
              <option value="team_lead">Team Lead</option>
              <option value="admin">Admin</option>
            </select>
          </div>
          
          <div className="form-group">
            <label htmlFor="department">Department</label>
            <input
              type="text"
              id="department"
              value={formData.department}
              onChange={(e) => handleInputChange('department', e.target.value)}
              placeholder="Engineering"
            />
          </div>
        </div>

        <div className="form-group">
          <label htmlFor="teamId">Team ID (optional)</label>
          <input
            type="text"
            id="teamId"
            value={formData.teamId}
            onChange={(e) => handleInputChange('teamId', e.target.value)}
            placeholder="team_123"
          />
        </div>

        <button type="submit" disabled={loading}>
          {loading ? 'Sending Invitation...' : 'Send Invitation'}
        </button>
      </form>
    </div>
  );
}

export default AdminInviteForm;

Bulk User Invitation

import { useState } from 'react';

function BulkInviteForm() {
  const [csvData, setCsvData] = useState('');
  const [loading, setLoading] = useState(false);
  const [results, setResults] = useState([]);

  const handleBulkInvite = async (e) => {
    e.preventDefault();
    setLoading(true);
    setResults([]);

    // Parse CSV data
    const lines = csvData.trim().split('\n');
    const headers = lines[0].split(',').map(h => h.trim());
    const invitations = [];

    for (let i = 1; i < lines.length; i++) {
      const values = lines[i].split(',').map(v => v.trim());
      const invitation = {};
      
      headers.forEach((header, index) => {
        invitation[header] = values[index];
      });
      
      invitations.push(invitation);
    }

    // Send invitations
    const inviteResults = [];
    
    for (const invitation of invitations) {
      try {
        const response = await fetch('/api/admin/invite', {
          method: 'POST',
          headers: {
            'Authorization': `Bearer ${process.env.REACT_APP_SERVICE_ROLE_KEY}`,
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            email: invitation.email,
            data: {
              first_name: invitation.first_name,
              last_name: invitation.last_name,
              role: invitation.role || 'user',
              department: invitation.department
            },
            redirect_to: 'https://yourapp.com/welcome'
          }),
        });

        if (response.ok) {
          const result = await response.json();
          inviteResults.push({
            email: invitation.email,
            success: true,
            userId: result.user.id
          });
        } else {
          const errorData = await response.json();
          inviteResults.push({
            email: invitation.email,
            success: false,
            error: errorData.msg
          });
        }
      } catch (err) {
        inviteResults.push({
          email: invitation.email,
          success: false,
          error: err.message
        });
      }
    }

    setResults(inviteResults);
    setLoading(false);
  };

  return (
    <div className="bulk-invite-form">
      <h2>Bulk User Invitation</h2>
      
      <div className="csv-instructions">
        <h3>CSV Format</h3>
        <p>Use the following format for your CSV data:</p>
        <pre>
email,first_name,last_name,role,department
john@example.com,John,Doe,team_member,Engineering
jane@example.com,Jane,Smith,admin,Marketing
        </pre>
      </div>

      <form onSubmit={handleBulkInvite} className="bulk-form">
        <div className="form-group">
          <label htmlFor="csvData">CSV Data</label>
          <textarea
            id="csvData"
            value={csvData}
            onChange={(e) => setCsvData(e.target.value)}
            placeholder="email,first_name,last_name,role,department&#10;john@example.com,John,Doe,team_member,Engineering"
            rows={10}
            required
          />
        </div>

        <button type="submit" disabled={loading}>
          {loading ? 'Sending Invitations...' : 'Send All Invitations'}
        </button>
      </form>

      {results.length > 0 && (
        <div className="results">
          <h3>Invitation Results</h3>
          <div className="results-summary">
            <span className="success-count">
{results.filter(r => r.success).length} successful
            </span>
            <span className="error-count">
{results.filter(r => !r.success).length} failed
            </span>
          </div>
          
          <div className="results-list">
            {results.map((result, index) => (
              <div key={index} className={`result-item ${result.success ? 'success' : 'error'}`}>
                <span className="email">{result.email}</span>
                {result.success ? (
                  <span className="status">✅ Invited (ID: {result.userId})</span>
                ) : (
                  <span className="error">{result.error}</span>
                )}
              </div>
            ))}
          </div>
        </div>
      )}
    </div>
  );
}

export default BulkInviteForm;

Node.js Backend Handler

const express = require('express');
const { body, validationResult } = require('express-validator');

const router = express.Router();

// Middleware to verify service role
const verifyServiceRole = (req, res, next) => {
  const authHeader = req.headers.authorization;
  const token = authHeader?.split(' ')[1];

  if (!token || token !== process.env.SERVICE_ROLE_KEY) {
    return res.status(401).json({
      code: 401,
      msg: 'Invalid service role key',
      details: 'Please provide a valid service role key'
    });
  }

  next();
};

router.post('/admin/invite', [
  verifyServiceRole,
  body('email').isEmail().normalizeEmail(),
  body('data').optional().isObject(),
  body('redirect_to').optional().isURL()
], async (req, res) => {
  try {
    // Check validation errors
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return res.status(400).json({
        code: 400,
        msg: 'Invalid request data',
        details: errors.array()
      });
    }

    const { email, data, redirect_to, options } = req.body;

    // Call Strike Auth Service
    const response = await fetch(`${process.env.AUTH_SERVICE_URL}/admin/invite`, {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${process.env.SERVICE_ROLE_KEY}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        email,
        data,
        redirect_to: redirect_to || `${process.env.APP_URL}/welcome`,
        options
      }),
    });

    const result = await response.json();

    if (!response.ok) {
      return res.status(response.status).json(result);
    }

    // Log invitation
    console.log(`User invited: ${email}, role: ${data?.role || 'user'}`);

    res.json(result);

  } catch (error) {
    console.error('Invitation error:', error);
    res.status(500).json({
      code: 500,
      msg: 'Internal server error',
      details: 'Please try again later'
    });
  }
});

module.exports = router;

Use Cases

Team Member Invitation

Invite new team members with specific roles:
{
  "email": "developer@example.com",
  "data": {
    "first_name": "Alex",
    "last_name": "Johnson",
    "role": "developer",
    "team_id": "engineering_team",
    "department": "Engineering",
    "start_date": "2023-02-01"
  },
  "redirect_to": "https://yourapp.com/onboarding"
}

Client Invitation

Invite external clients with limited access:
{
  "email": "client@company.com",
  "data": {
    "first_name": "Sarah",
    "last_name": "Wilson",
    "role": "client",
    "company": "Wilson Corp",
    "access_level": "read_only"
  },
  "redirect_to": "https://yourapp.com/client-portal"
}

Admin Invitation

Invite administrators with elevated privileges:
{
  "email": "admin@example.com",
  "data": {
    "first_name": "Michael",
    "last_name": "Brown",
    "role": "admin",
    "permissions": ["user_management", "system_config"],
    "department": "IT"
  },
  "redirect_to": "https://yourapp.com/admin-setup"
}

Security Features

  • Service Role Authentication: Requires valid service role key
  • Email Validation: Validates email format and uniqueness
  • Rate Limiting: Prevents invitation spam
  • Invitation Expiration: Invitations expire after 7 days
  • Audit Logging: Logs all invitation activities

Best Practices

  • Include meaningful user data in invitations
  • Use appropriate redirect URLs for different user types
  • Set reasonable expiration times for invitations
  • Track invitation status and follow up if needed
  • Provide clear invitation emails with next steps
  • Include contact information for support
  • Customize invitation content based on user role
  • Handle expired invitations gracefully
  • Validate all user data before processing
  • Implement proper access controls for invitation sending
  • Monitor for suspicious invitation patterns
  • Use secure redirect URLs (HTTPS only)

Testing

Unit Tests

describe('POST /admin/invite', () => {
  test('should send invitation to new user', async () => {
    const inviteData = {
      email: 'newuser@example.com',
      data: {
        first_name: 'John',
        last_name: 'Doe',
        role: 'team_member'
      }
    };

    const response = await request(app)
      .post('/admin/invite')
      .set('Authorization', 'Bearer valid_service_role_key')
      .send(inviteData)
      .expect(200);

    expect(response.body.user.email).toBe('newuser@example.com');
    expect(response.body.user.user_metadata.first_name).toBe('John');
    expect(response.body.message_id).toBeDefined();
  });

  test('should reject invitation for existing user', async () => {
    await request(app)
      .post('/admin/invite')
      .set('Authorization', 'Bearer valid_service_role_key')
      .send({
        email: 'existing@example.com',
        data: { first_name: 'Test' }
      })
      .expect(409);
  });

  test('should reject invalid service role', async () => {
    await request(app)
      .post('/admin/invite')
      .set('Authorization', 'Bearer invalid_key')
      .send({
        email: 'test@example.com',
        data: { first_name: 'Test' }
      })
      .expect(401);
  });
});

Create User

Create user accounts directly

Generate Link

Generate custom invitation links

Update User

Update user information

Verify Invitation

Accept user invitations