HRIS Integration - Users API Guide

System Features Special Features Api
Last updated: January 26, 2026 • Version: 1.0

HRIS Integration - Users API Guide

Complete guide for external HRIS systems integrating with MangoApps to retrieve user and employee data in bulk. This API enables seamless synchronization of employee information between MangoApps and external HRIS platforms.

Target Audience: Developers building HRIS integrations with MangoApps
Prerequisites: Admin access to create API tokens with read:users scope
API Version: v1


Overview

What is the HRIS Users API?

The HRIS Users API provides external HRIS systems with programmatic access to retrieve user and employee data from MangoApps. This enables:

  • Bulk User Retrieval: Fetch multiple users with pagination and filtering
  • Individual User Lookup: Find specific users by unique identifier (UID) or email
  • Incremental Synchronization: Retrieve only users updated since a specific timestamp
  • Comprehensive Data: Access complete employee profiles including employment details, manager relationships, and organizational data
  • Real-time Updates: Subscribe to webhooks for user lifecycle events

Why Use This API?

  • Efficient Synchronization: Bulk retrieval with pagination handles large user bases
  • Definitive UID: Uses mango_employee_id (E1234567 format) as primary identifier
  • Business Isolation: Automatically scoped to your business - no cross-business data leakage
  • Audit Trail: All API access is logged for security and compliance
  • Rate Limited: Built-in rate limiting protects system performance
  • Webhook Support: Real-time notifications for user changes

Key Features

  • âś… Bulk Retrieval: Get multiple users with pagination (up to 100 per page)
  • âś… Multiple Lookup Methods: Find users by UID (mango_employee_id) or email
  • âś… Advanced Filtering: Filter by active status, department, employment status, search terms
  • âś… Incremental Sync: updated_after parameter for efficient synchronization
  • âś… Soft Deletion Support: Include deleted users for compliance/auditing
  • âś… Sorting: Sort by name, created_at, updated_at
  • âś… Webhooks: Real-time notifications for user.created, user.updated, user.activated, user.deactivated, user.deleted

Complete API Call Flow

High-Level Flow Diagram

sequenceDiagram participant HRIS as External HRIS System participant API as MangoApps API participant Token as API Token participant DB as MangoApps Database participant Webhook as Webhook Service HRIS->>API: 1. Create API Token Note over HRIS,API: Admin creates token with 'read:users' scope API-->>HRIS: API Token HRIS->>API: 2. Bulk User Retrieval Note over HRIS,API: GET /api/v1/users?page=1&per_page=50 API->>Token: Validate Token & Scope Token->>API: Valid with 'read:users' scope API->>DB: Query Users (Business Scoped) DB-->>API: User Data API-->>HRIS: Paginated User List + Meta HRIS->>API: 3. Individual User Lookup Note over HRIS,API: GET /api/v1/users/E1000001 API->>Token: Validate Token API->>DB: Find User by UID DB-->>API: User Details API-->>HRIS: Complete User Profile Note over Webhook: User Updated in MangoApps Webhook->>HRIS: POST user.updated Event Note over HRIS,Webhook: Real-time notification

Step-by-Step Implementation

Step 1: Create an API Token

Prerequisites:

  • Admin access to MangoApps
  • Understanding of required permission scopes

Process:

  1. Navigate to Admin Interface
    • Go to Admin → API → Tokens
    • Click Create API Token (or Create Service Account Token for system integrations)
  2. Configure Token Settings
    • Name: Descriptive name (e.g., “HRIS Integration - User Sync”)
    • Scopes: Select admin or read:users scope
      • admin: Full access including user management
      • read:users: Read-only access to user data (recommended for HRIS integrations)
    • Service Account: Check this box for system-to-system integrations (optional but recommended)
    • Expiration: Set appropriate expiration date
    • Business: Automatically set to your current business
  3. Save and Copy Token
    • Click Create Token
    • IMPORTANT: Copy the Token immediately
    • This value is shown only once and cannot be retrieved later
    • If using a service account, also copy the Secret

Example Response:

{
  "token": "abc123def456ghi789jkl012mno345pqr678",
  "secret": "sec_xyz789uvw456rst123",  // Only for service accounts
  "name": "HRIS Integration - User Sync",
  "scopes": ["read:users"],
  "expires_at": "2026-01-15T10:30:00Z"
}

Security Notes:

  • Store tokens securely (environment variables, secret managers)
  • Never commit tokens to version control
  • Use different tokens for different environments (dev/staging/prod)
  • Rotate tokens periodically (every 6-12 months)

Step 2: Retrieve Users in Bulk

Endpoint: GET /api/v1/users

Authentication: Use your API token

Request Headers:

Authorization: Bearer YOUR_API_TOKEN
Content-Type: application/json
Accept: application/json

Query Parameters:

Parameter Type Required Description Default Example
page integer No Page number for pagination 1 ?page=2
per_page integer No Number of users per page (max 100) 25 ?per_page=50
active boolean No Filter by active status all ?active=true
search string No Search by name or email - ?search=john
department string No Filter by department name - ?department=Engineering
employment_status string No Filter by employment status - ?employment_status=full_time
updated_after datetime No Only users updated after this time (ISO 8601) - ?updated_after=2025-11-17T00:00:00Z
include_deleted boolean No Include soft-deleted users false ?include_deleted=true
sort_by string No Sort field: name, created_at, updated_at created_at ?sort_by=name
sort_order string No Sort direction: asc, desc desc ?sort_order=asc

Example Request (cURL):

curl -X GET "https://your-domain.com/api/v1/users?page=1&per_page=50&active=true" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json"

Example Response:

{
  "users": [
    {
      "uid": "E1000001",
      "id": 1753,
      "email": "alice.anderson@example.com",
      "first_name": "Alice",
      "last_name": "Anderson",
      "full_name": "Alice Anderson",
      "active": true,
      "job_title": "Senior Engineer",
      "department": "Engineering",
      "organizational_role": "Staff",
      "hire_date": "2020-01-15",
      "original_hire_date": "2020-01-15",
      "employment_status": "full_time",
      "employee_type": "Full-Time",
      "phone": "+1-415-555-2847",
      "manager": {
        "uid": "E1000002",
        "email": "manager@example.com",
        "full_name": "Manager Name"
      },
      "locations": ["San Francisco Office"],
      "termination_date": null,
      "termination_type": null,
      "created_at": "2025-10-13T23:23:24Z",
      "updated_at": "2025-11-14T18:17:29Z",
      "last_activity_at": "2025-11-14T18:17:29Z"
    }
  ],
  "meta": {
    "total_count": 37,
    "total_pages": 1,
    "current_page": 1,
    "per_page": 50
  }
}

Response Fields:

Each user object includes:

  • uid: Unique mango_employee_id (E1234567 format) - Primary identifier for HRIS systems
  • id: Internal MangoApps user ID (integer)
  • email: User’s email address
  • first_name, last_name, full_name: Name fields
  • active: Boolean indicating if user is active
  • job_title: Job position/title
  • department: Department name
  • organizational_role: Role (Administrator, Manager, Staff, etc.)
  • hire_date: Original hire date
  • original_hire_date: Original hire date (if different from hire_date)
  • employment_status: full_time, part_time, contractor, etc.
  • employee_type: Full-Time, Part-Time, Contractor, etc.
  • phone: Phone number
  • manager: Manager information (if applicable)
  • locations: Array of location names
  • termination_date: Termination date (if terminated)
  • termination_type: Termination type (if terminated)
  • created_at, updated_at, last_activity_at: Timestamps

Pagination:

The meta object provides pagination information:

  • total_count: Total number of users matching the query
  • total_pages: Total number of pages
  • current_page: Current page number
  • per_page: Number of users per page

Example: Pagination Loop

page = 1
all_users = []

while True:
    response = requests.get(
        f'{base_url}/api/v1/users',
        headers={'Authorization': f'Bearer {token}'},
        params={'page': page, 'per_page': 50}
    )
    
    data = response.json()
    all_users.extend(data['users'])
    
    if page >= data['meta']['total_pages']:
        break
    
    page += 1

Step 3: Retrieve Individual User by UID

Endpoint: GET /api/v1/users/:uid

Authentication: Use your API token

Parameters:

  • uid: The mango_employee_id (e.g., “E1000001”) - Primary identifier

Example Request:

curl -X GET "https://your-domain.com/api/v1/users/E1000001" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json"

Example Response:

{
  "user": {
    "uid": "E1000001",
    "id": 1753,
    "email": "alice.anderson@example.com",
    "first_name": "Alice",
    "last_name": "Anderson",
    "full_name": "Alice Anderson",
    "active": true,
    "job_title": "Senior Engineer",
    "department": "Engineering",
    "organizational_role": "Staff",
    "hire_date": "2020-01-15",
    "employment_status": "full_time",
    "employee_type": "Full-Time",
    "phone": "+1-415-555-2847",
    "manager": {
      "uid": "E1000002",
      "email": "manager@example.com",
      "full_name": "Manager Name",
      "job_title": "Engineering Manager"
    },
    "direct_reports_count": 5,
    "locations": [
      {
        "name": "San Francisco Office",
        "address": "123 Main St, San Francisco, CA"
      }
    ],
    "organizational_department": {
      "name": "Engineering",
      "code": "ENG"
    },
    "flsa_status": "exempt",
    "worker_classification": "employee",
    "time_zone": "America/Los_Angeles",
    "external_employee_id": "EMP-12345",
    "skills_count": 8,
    "certifications_count": 3,
    "termination_date": null,
    "termination_type": null,
    "created_at": "2025-10-13T23:23:24Z",
    "updated_at": "2025-11-14T18:17:29Z",
    "last_activity_at": "2025-11-14T18:17:29Z"
  }
}

Additional Fields in Detail Response:

  • manager: Detailed manager information (not just basic info)
  • direct_reports_count: Number of direct reports
  • locations: Detailed location objects with addresses
  • organizational_department: Detailed department information
  • flsa_status: FLSA exemption status
  • worker_classification: Worker classification
  • time_zone: User’s timezone
  • external_employee_id: External system’s employee ID (if set)
  • skills_count: Number of skills
  • certifications_count: Number of certifications

Error Responses:

404 Not Found - User Not Found:

{
  "error": {
    "code": "user_not_found",
    "message": "User with UID 'E9999999' not found in current business"
  }
}

Step 4: Retrieve Individual User by Email

Endpoint: GET /api/v1/users/by_email/:email

Authentication: Use your API token

Parameters:

  • email: The user’s email address

Example Request:

curl -X GET "https://your-domain.com/api/v1/users/by_email/alice.anderson@example.com" \
  -H "Authorization: Bearer YOUR_API_TOKEN" \
  -H "Content-Type: application/json"

Example Response:

Same format as individual user lookup by UID (detailed response).

Error Responses:

404 Not Found - User Not Found:

{
  "error": {
    "code": "user_not_found",
    "message": "User with email 'nonexistent@example.com' not found in current business"
  }
}

Complete Integration Example

Python Example

import requests
import os
from datetime import datetime, timedelta
from typing import List, Dict, Optional

class MangoAppsHRISClient:
    def __init__(self, base_url: str, api_token: str):
        """
        Initialize HRIS API client
        
        Args:
            base_url: MangoApps API base URL (e.g., 'https://your-domain.com')
            api_token: API token with 'read:users' scope
        """
        self.base_url = base_url.rstrip('/')
        self.api_token = api_token
        self.headers = {
            'Authorization': f'Bearer {api_token}',
            'Content-Type': 'application/json',
            'Accept': 'application/json'
        }
    
    def get_all_users(self, 
                     active_only: bool = True,
                     per_page: int = 50,
                     include_deleted: bool = False) -> List[Dict]:
        """
        Retrieve all users with pagination
        
        Args:
            active_only: Only return active users
            per_page: Number of users per page (max 100)
            include_deleted: Include soft-deleted users
        
        Returns:
            List of user dictionaries
        """
        all_users = []
        page = 1
        
        while True:
            params = {
                'page': page,
                'per_page': min(per_page, 100),  # Cap at 100
                'active': 'true' if active_only else None,
                'include_deleted': 'true' if include_deleted else None
            }
            
            # Remove None values
            params = {k: v for k, v in params.items() if v is not None}
            
            response = requests.get(
                f'{self.base_url}/api/v1/users',
                headers=self.headers,
                params=params
            )
            response.raise_for_status()
            
            data = response.json()
            all_users.extend(data['users'])
            
            # Check if we've reached the last page
            if page >= data['meta']['total_pages']:
                break
            
            page += 1
        
        return all_users
    
    def get_user_by_uid(self, uid: str) -> Dict:
        """
        Retrieve a single user by mango_employee_id (UID)
        
        Args:
            uid: mango_employee_id (e.g., 'E1000001')
        
        Returns:
            User dictionary with detailed information
        """
        response = requests.get(
            f'{self.base_url}/api/v1/users/{uid}',
            headers=self.headers
        )
        response.raise_for_status()
        
        return response.json()['user']
    
    def get_user_by_email(self, email: str) -> Dict:
        """
        Retrieve a single user by email address
        
        Args:
            email: User's email address
        
        Returns:
            User dictionary with detailed information
        """
        response = requests.get(
            f'{self.base_url}/api/v1/users/by_email/{email}',
            headers=self.headers
        )
        response.raise_for_status()
        
        return response.json()['user']
    
    def get_users_updated_since(self, 
                                since: datetime,
                                per_page: int = 50) -> List[Dict]:
        """
        Retrieve users updated since a specific timestamp (incremental sync)
        
        Args:
            since: Datetime object for the cutoff time
            per_page: Number of users per page
        
        Returns:
            List of user dictionaries updated since the timestamp
        """
        all_users = []
        page = 1
        
        # Format datetime as ISO 8601
        updated_after = since.strftime('%Y-%m-%dT%H:%M:%SZ')
        
        while True:
            params = {
                'page': page,
                'per_page': min(per_page, 100),
                'updated_after': updated_after
            }
            
            response = requests.get(
                f'{self.base_url}/api/v1/users',
                headers=self.headers,
                params=params
            )
            response.raise_for_status()
            
            data = response.json()
            all_users.extend(data['users'])
            
            if page >= data['meta']['total_pages']:
                break
            
            page += 1
        
        return all_users
    
    def search_users(self, 
                    search_term: str,
                    department: Optional[str] = None,
                    employment_status: Optional[str] = None) -> List[Dict]:
        """
        Search users by name or email with optional filters
        
        Args:
            search_term: Search query (searches name and email)
            department: Filter by department name
            employment_status: Filter by employment status
        
        Returns:
            List of matching user dictionaries
        """
        params = {
            'search': search_term,
            'per_page': 100
        }
        
        if department:
            params['department'] = department
        if employment_status:
            params['employment_status'] = employment_status
        
        response = requests.get(
            f'{self.base_url}/api/v1/users',
            headers=self.headers,
            params=params
        )
        response.raise_for_status()
        
        return response.json()['users']


# Usage Example
if __name__ == '__main__':
    # Initialize client
    client = MangoAppsHRISClient(
        base_url='https://your-domain.com',
        api_token=os.getenv('MANGOOPS_API_TOKEN')
    )
    
    # Example 1: Get all active users
    print("Fetching all active users...")
    all_users = client.get_all_users(active_only=True)
    print(f"Found {len(all_users)} active users")
    
    # Example 2: Get user by UID
    print("\nFetching user by UID...")
    user = client.get_user_by_uid('E1000001')
    print(f"User: {user['full_name']} ({user['email']})")
    
    # Example 3: Incremental sync (users updated in last 24 hours)
    print("\nFetching users updated in last 24 hours...")
    since = datetime.utcnow() - timedelta(days=1)
    updated_users = client.get_users_updated_since(since)
    print(f"Found {len(updated_users)} updated users")
    
    # Example 4: Search users
    print("\nSearching users...")
    results = client.search_users('john', department='Engineering')
    print(f"Found {len(results)} matching users")

JavaScript/Node.js Example

const axios = require('axios');

class MangoAppsHRISClient {
  constructor(baseUrl, apiToken) {
    this.baseUrl = baseUrl.replace(/\/$/, '');
    this.apiToken = apiToken;
    this.headers = {
      'Authorization': `Bearer ${apiToken}`,
      'Content-Type': 'application/json',
      'Accept': 'application/json'
    };
  }

  async getAllUsers(options = {}) {
    const {
      activeOnly = true,
      perPage = 50,
      includeDeleted = false
    } = options;

    const allUsers = [];
    let page = 1;

    while (true) {
      const params = {
        page,
        per_page: Math.min(perPage, 100),
        ...(activeOnly && { active: 'true' }),
        ...(includeDeleted && { include_deleted: 'true' })
      };

      const response = await axios.get(
        `${this.baseUrl}/api/v1/users`,
        { headers: this.headers, params }
      );

      const { users, meta } = response.data;
      allUsers.push(...users);

      if (page >= meta.total_pages) break;
      page++;
    }

    return allUsers;
  }

  async getUserByUID(uid) {
    const response = await axios.get(
      `${this.baseUrl}/api/v1/users/${uid}`,
      { headers: this.headers }
    );
    return response.data.user;
  }

  async getUserByEmail(email) {
    const response = await axios.get(
      `${this.baseUrl}/api/v1/users/by_email/${email}`,
      { headers: this.headers }
    );
    return response.data.user;
  }

  async getUsersUpdatedSince(since, perPage = 50) {
    const allUsers = [];
    let page = 1;

    const updatedAfter = since.toISOString();

    while (true) {
      const response = await axios.get(
        `${this.baseUrl}/api/v1/users`,
        {
          headers: this.headers,
          params: {
            page,
            per_page: Math.min(perPage, 100),
            updated_after: updatedAfter
          }
        }
      );

      const { users, meta } = response.data;
      allUsers.push(...users);

      if (page >= meta.total_pages) break;
      page++;
    }

    return allUsers;
  }
}

// Usage Example
(async () => {
  const client = new MangoAppsHRISClient(
    'https://your-domain.com',
    process.env.MANGOOPS_API_TOKEN
  );

  // Get all active users
  const users = await client.getAllUsers({ activeOnly: true });
  console.log(`Found ${users.length} active users`);

  // Get user by UID
  const user = await client.getUserByUID('E1000001');
  console.log(`User: ${user.full_name} (${user.email})`);

  // Incremental sync
  const since = new Date();
  since.setDate(since.getDate() - 1);
  const updatedUsers = await client.getUsersUpdatedSince(since);
  console.log(`Found ${updatedUsers.length} updated users`);
})();

Webhooks for Real-Time Updates

Setting Up Webhooks

Webhooks provide real-time notifications when user data changes in MangoApps. This eliminates the need for polling and enables immediate synchronization.

1. Create a Webhook

Navigate to Admin → API → Webhooks → New Webhook

Configuration:

  • Name: Descriptive name (e.g., “HRIS User Sync Webhook”)
  • URL: Your webhook endpoint URL
  • Event Types: Select user-related events:
    • user.created - New user created
    • user.updated - User information updated
    • user.activated - User activated
    • user.deactivated - User deactivated
    • user.deleted - User soft-deleted
  • Active: Enable the webhook

2. Webhook Payload Format

user.created Event:

{
  "event": "user.created",
  "timestamp": "2025-11-18T15:30:00Z",
  "data": {
    "uid": "E1000001",
    "user": {
      "uid": "E1000001",
      "email": "alice.anderson@example.com",
      "first_name": "Alice",
      "last_name": "Anderson",
      "full_name": "Alice Anderson",
      "active": true,
      "job_title": "Senior Engineer",
      "employment_status": "full_time",
      "employee_type": "Full-Time"
    }
  }
}

user.updated Event:

{
  "event": "user.updated",
  "timestamp": "2025-11-18T15:30:00Z",
  "data": {
    "uid": "E1000001",
    "changes": ["job_title", "department"],
    "user": {
      "uid": "E1000001",
      "email": "alice.anderson@example.com",
      "first_name": "Alice",
      "last_name": "Anderson",
      "full_name": "Alice Anderson",
      "active": true,
      "job_title": "Engineering Manager",
      "employment_status": "full_time",
      "employee_type": "Full-Time"
    }
  }
}

user.deleted Event:

{
  "event": "user.deleted",
  "timestamp": "2025-11-18T15:30:00Z",
  "data": {
    "uid": "E1000001",
    "user": {
      "uid": "E1000001",
      "email": "alice.anderson@example.com",
      "first_name": "Alice",
      "last_name": "Anderson",
      "full_name": "Alice Anderson"
    },
    "deleted_at": "2025-11-18T15:30:00Z",
    "deleted_by": "admin@example.com"
  }
}

3. Webhook Security

Webhooks include a signature header for verification:

X-MangoApps-Signature: sha256=abc123def456...

Verify the signature using your webhook secret to ensure the request is authentic.

4. Webhook Retry Logic

MangoApps automatically retries failed webhook deliveries with exponential backoff:

  • 1 minute
  • 5 minutes
  • 15 minutes
  • 1 hour
  • 4 hours

After 5 consecutive failures, the webhook is automatically deactivated.


Common Use Cases

Use Case 1: Initial User Synchronization

Scenario: Sync all users from MangoApps to your HRIS system on first integration

# Get all active users
client = MangoAppsHRISClient(base_url, api_token)
all_users = client.get_all_users(active_only=True)

# Process each user
for user in all_users:
    # Use user['uid'] as the primary identifier
    sync_user_to_hris(user['uid'], user)
    print(f"Synced user: {user['full_name']} ({user['uid']})")

Use Case 2: Incremental Synchronization

Scenario: Sync only users updated since last sync (efficient for regular syncs)

# Get last sync timestamp from your system
last_sync = get_last_sync_timestamp()

# Get users updated since last sync
client = MangoAppsHRISClient(base_url, api_token)
updated_users = client.get_users_updated_since(last_sync)

# Process updates
for user in updated_users:
    update_user_in_hris(user['uid'], user)
    print(f"Updated user: {user['full_name']} ({user['uid']})")

# Update last sync timestamp
update_last_sync_timestamp(datetime.utcnow())

Use Case 3: User Lookup by Email

Scenario: Find a user in MangoApps when you only have their email address

client = MangoAppsHRISClient(base_url, api_token)

try:
    user = client.get_user_by_email('alice.anderson@example.com')
    print(f"Found user: {user['full_name']} (UID: {user['uid']})")
    # Use user['uid'] for future lookups
except requests.exceptions.HTTPError as e:
    if e.response.status_code == 404:
        print("User not found")
    else:
        raise

Use Case 4: Department-Based Filtering

Scenario: Sync users from a specific department

client = MangoAppsHRISClient(base_url, api_token)

# Get all users in Engineering department
response = requests.get(
    f'{client.base_url}/api/v1/users',
    headers=client.headers,
    params={
        'department': 'Engineering',
        'per_page': 100,
        'active': 'true'
    }
)

engineering_users = response.json()['users']
print(f"Found {len(engineering_users)} Engineering users")

Use Case 5: Webhook-Based Real-Time Sync

Scenario: Use webhooks for immediate synchronization when users change

from flask import Flask, request, jsonify
import hmac
import hashlib

app = Flask(__name__)
WEBHOOK_SECRET = os.getenv('WEBHOOK_SECRET')

@app.route('/webhook/mangoops', methods=['POST'])
def mangoapps_webhook():
    # Verify signature
    signature = request.headers.get('X-MangoApps-Signature')
    if not verify_signature(request.data, signature):
        return jsonify({'error': 'Invalid signature'}), 401
    
    event = request.json
    
    if event['event'] == 'user.created':
        # New user - sync to HRIS
        sync_user_to_hris(event['data']['uid'], event['data']['user'])
    
    elif event['event'] == 'user.updated':
        # User updated - update in HRIS
        update_user_in_hris(event['data']['uid'], event['data']['user'])
    
    elif event['event'] == 'user.deleted':
        # User deleted - mark as deleted in HRIS
        mark_user_deleted_in_hris(event['data']['uid'])
    
    return jsonify({'status': 'ok'}), 200

def verify_signature(payload, signature):
    expected = hmac.new(
        WEBHOOK_SECRET.encode(),
        payload,
        hashlib.sha256
    ).hexdigest()
    return hmac.compare_digest(f'sha256={expected}', signature)

Security Best Practices

API Token Security

  1. Use Least Privilege: Only grant read:users scope if that’s all you need
  2. Separate Environments: Use different tokens for dev/staging/prod
  3. Regular Rotation: Rotate tokens periodically (every 6-12 months)
  4. Secure Storage: Store tokens in environment variables or secret managers
  5. Monitor Usage: Regularly review API access logs in Admin → API → Audit Logs

API Call Security

  1. HTTPS Only: Always use HTTPS for API calls
  2. Error Handling: Don’t expose tokens in error messages
  3. Rate Limiting: Respect API rate limits (100 requests/hour for bulk endpoints)
  4. Request Validation: Validate all input before making API calls
  5. Logging: Log API calls but never log tokens

Webhook Security

  1. Verify Signatures: Always verify webhook signatures
  2. HTTPS Endpoints: Use HTTPS for webhook endpoints
  3. Idempotency: Handle duplicate webhook deliveries gracefully
  4. Timeout Handling: Set appropriate timeouts for webhook processing
  5. Error Handling: Return proper HTTP status codes

Troubleshooting

Issue: “Invalid or expired authentication token” Error

Problem: Getting 401 error when making API calls
Solution:

  • Verify your API token is correct
  • Check that token hasn’t expired
  • Ensure token has read:users or admin scope
  • Verify you’re using Authorization: Bearer TOKEN header format

Issue: “Insufficient scope” Error

Problem: Getting 403 error about insufficient scope
Solution:

  • Verify API token has read:users or admin scope
  • Create new API token with correct scopes
  • Check token configuration in Admin → API → Tokens

Issue: “User not found” Error

Problem: Getting 404 error when looking up user
Solution:

  • Verify user exists in your business
  • Check that user belongs to the same business as API token
  • Ensure UID format is correct (E1234567)
  • Verify email address is correct and user exists

Issue: Empty Results from Bulk Retrieval

Problem: Getting empty users array
Solution:

  • Check that your business has users
  • Verify filters aren’t too restrictive
  • Try removing filters to see all users
  • Check include_deleted parameter if looking for deleted users

Issue: Rate Limiting Errors

Problem: Getting 429 Too Many Requests errors
Solution:

  • Implement exponential backoff retry logic
  • Reduce request frequency
  • Use pagination efficiently (larger per_page values)
  • Consider using webhooks instead of polling

Issue: Webhook Not Receiving Events

Problem: Webhook endpoint not receiving events
Solution:

  • Verify webhook is active in Admin → API → Webhooks
  • Check webhook URL is accessible from internet
  • Verify webhook endpoint returns 200 status code
  • Check webhook delivery logs in Admin → API → Deliveries
  • Ensure webhook has correct event types selected

API Reference Summary

Endpoints

Method Endpoint Description Auth Required
GET /api/v1/users Bulk user retrieval with pagination and filtering read:users or admin
GET /api/v1/users/:uid Individual user lookup by mango_employee_id read:users or admin
GET /api/v1/users/by_email/:email Individual user lookup by email read:users or admin

Required Scopes

Scope Description
read:users Read-only access to user data (recommended for HRIS integrations)
admin Full access including user management

Rate Limits

Endpoint Limit
Bulk retrieval (GET /api/v1/users) 100 requests/hour per API token
Individual lookup (GET /api/v1/users/:uid, /by_email/:email) 300 requests/hour per API token

Response Status Codes

Code Description
200 Success
401 Unauthorized - Invalid or expired token
403 Forbidden - Insufficient scope
404 Not Found - User not found
429 Too Many Requests - Rate limit exceeded
500 Internal Server Error

Additional Resources

  • API Authentication Guide: Complete authentication system overview
  • API Configuration: Setting up API access and tokens
  • System Account Impersonation Guide: Using service accounts for advanced integrations
  • Custom API: Building custom API endpoints

Developer Tools

  • OpenAPI Specification: /api/v1/docs - Interactive API documentation
  • API Testing: Use ./api test-hris-users command for comprehensive testing
  • Postman Collection: Available in API documentation

Support

  • API Documentation: Available at /admin/api/documentation
  • API Analytics: Monitor usage at /admin/api/analytics
  • Webhook Logs: View delivery logs at /admin/api/webhook_deliveries
  • Audit Logs: Review API access at /admin/api/audit_logs

Quick Reference

Complete Flow Checklist

  • âś… Create API token with read:users scope
  • âś… Store token securely (environment variables)
  • âś… Implement bulk user retrieval with pagination
  • âś… Use mango_employee_id (UID) as primary identifier
  • âś… Implement incremental sync with updated_after parameter
  • âś… Set up webhooks for real-time updates
  • âś… Handle errors gracefully (404, 401, 403, 429)
  • âś… Implement rate limiting retry logic
  • âś… Monitor API usage and audit logs
  • âś… Rotate tokens periodically

Code Snippet Template

# 1. Initialize client
client = MangoAppsHRISClient(base_url, api_token)

# 2. Get all users (with pagination)
all_users = client.get_all_users(active_only=True)

# 3. Get user by UID
user = client.get_user_by_uid('E1000001')

# 4. Incremental sync
since = datetime.utcnow() - timedelta(days=1)
updated_users = client.get_users_updated_since(since)

# 5. Use UID as primary identifier in your HRIS system
for user in all_users:
    sync_to_hris(user['uid'], user)

Key Identifiers

  • Primary UID: mango_employee_id (format: E1234567) - Use this as the definitive identifier
  • Fallback ID: id (integer) - Internal MangoApps ID
  • Alternative: external_employee_id - If your system provides its own ID
  • Lookup: email - For email-based lookups

This guide covers using the HRIS Users API for external system integrations. For general API authentication, see the API Authentication Guide. For advanced integrations with impersonation, see the System Account Impersonation Guide. Last updated: November 2025.