Zluri SDK Documentation V2

Zluri SDK Documentation

Welcome to the Zluri SDK documentation. This guide will help you integrate your custom applications and push data to Zluri when native connectors are not available.

Table of Contents

What is Zluri SDK?

The Zluri SDK is a set of APIs that enables customers to push data from their custom applications into the Zluri platform. It provides a standardized way to sync user information, application usage, transactions, and other critical SaaS management data when a native integration is not available.

Key Features

  • Custom Integration Support: Build integrations for applications not natively supported by Zluri
  • Flexible Data Upload: Support for multiple data entities (users, applications, licenses, roles, etc.)
  • Chunked Uploads: Handle large datasets efficiently with paginated uploads
  • Schema Validation: Automatic validation ensures data quality
  • Real-time Sync Status: Monitor the progress of your data syncs
  • Error Handling: Detailed error reports for troubleshooting

Why Use Zluri SDK?

Common Use Cases

  1. Custom or Internal Applications: Integrate proprietary tools built in-house
  2. Legacy Systems: Connect older applications that don't have modern APIs
  3. Specialized Software: Sync data from niche applications specific to your industry
  4. Data Consolidation: Aggregate data from multiple sources before sending to Zluri

Benefits

  • No Native Connector Required: Push data even when Zluri doesn't have a pre-built integration
  • Full Control: You manage when and what data to sync
  • Scalable: Handle datasets of any size with chunked uploads
  • Reliable: Built-in validation and error handling ensure data integrity

Getting Started

Prerequisites

  • Access to Zluri dashboard
  • Administrative privileges to generate API keys
  • Basic understanding of REST APIs
  • Programming knowledge in any language that supports HTTP requests

Step 1: Generate an API Key

  1. Log in to your Zluri dashboard
  2. Navigate to SettingsAPI Keys
  3. Click Generate New API Key
  4. Name your key (e.g., "SDK Integration Key")
  5. Copy and securely store the generated key
⚠️

Important: API keys are shown only once. Store them securely and never share them publicly.

Step 2: Choose Your Application

Before creating an instance, you need to identify the application you want to integrate:

  • Use the appId (orgAppId) of the application
  • If the application doesn't exist in Zluri, you may need to create it first

Step 3: Understand the Data Flow

graph LR
    A[Generate API Key] --> B[Create Instance]
    B --> C[Initialize Sync]
    C --> D[Upload Data Chunks]
    D --> E[Finish Sync]
    E --> F[Data Processing]
    F --> G[View in Zluri]

API Reference

Base URL

https://api.zluri.com

Authentication

All API requests must include your API key in the header:

Authorization: Bearer YOUR_API_KEY

1. Create Instance

Creates an SDK instance for syncing data with a specific application.

Endpoint

POST /ext/integrations/sync-sdk/v2/instances

Request Headers

{
  "Authorization": "Bearer YOUR_API_KEY",
  "Content-Type": "application/json"
}

Request Body

{
  "appId": "0365e45164d9fa3b3e274122",
  "instanceName": "my-custom-app-integration",
  "notificationEmails": ["[email protected]", "[email protected]"]
}

Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| appId | string | Yes | The orgAppId of the application |
| instanceName | string | Yes | Unique name for this integration instance |
| notificationEmails | array | No | Email addresses for sync notifications |

Response

{
  "data": {
    "instanceId": "651c2235e0eda4199c32e2dd",
    "name": "my-custom-app-integration",
    "status": "connected"
  }
}

Example Code (Python)

import requests

api_key = "YOUR_API_KEY"
base_url = "https://api.zluri.com"

headers = {
    "Authorization": f"Bearer {api_key}",
    "Content-Type": "application/json"
}

instance_data = {
    "appId": "0365e45164d9fa3b3e274122",
    "instanceName": "my-custom-app-integration",
    "notificationEmails": ["[email protected]"]
}

response = requests.post(
    f"{base_url}/ext/integrations/sync-sdk/v2/instances",
    json=instance_data,
    headers=headers
)

instance_id = response.json()["data"]["instanceId"]
print(f"Instance created with ID: {instance_id}")

2. List Instances

Retrieve all SDK instances for your organization.

Endpoint

GET /ext/integrations/sync-sdk/v2/instances

Query Parameters
| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| offset | integer | 0 | Pagination offset |
| limit | integer | 50 | Number of results (max: 100) |
| status | string | connected | Filter by status (connected, not-connected, error) |

Response

[
  {
    "instanceId": "651bb1396744c91d8886167",
    "name": "my-custom-app-integration",
    "appId": "0365e45164d9fa3b3e274122",
    "status": "connected"
  }
]

3. Initialize Sync

Start a new sync session for uploading data.

Endpoint

POST /ext/integrations/sync-sdk/v2/instances/{instanceId}/syncs

Response

{
  "syncId": "684b7697a29ac1902134fd8b",
  "message": "sync is successfully initiated"
}

Example Code (Python)

# Initialize a new sync
sync_response = requests.post(
    f"{base_url}/ext/integrations/sync-sdk/v2/instances/{instance_id}/syncs",
    headers=headers
)

sync_id = sync_response.json()["syncId"]
print(f"Sync initiated with ID: {sync_id}")

4. Upload Data - Snapshots

Upload snapshot data (current state) for entities like users, applications, licenses, etc.

Endpoint

POST /ext/integrations/sync-sdk/v2/syncs/{syncId}/data/snapshots

Request Body

{
  "entityName": "users",
  "pageNumber": 1,
  "data": [
    {
      "email": "[email protected]",
      "name": "John Doe",
      "department": "Engineering",
      "isActive": true,
      "jobTitle": "Senior Software Engineer",
      "reportingManagerEmail": "[email protected]",
      "createdAt": "2023-06-10T08:45:00Z"
    }
  ]
}

Supported Entity Names for Snapshots

  • users
  • applications
  • licenses
  • roles
  • groups
  • group_users
  • group_applications

Response

{
  "message": "data uploaded successfully",
  "totalRecordsUploaded": 1
}

Example Code (Python) - Chunked Upload

import math

def upload_users_in_chunks(users_data, sync_id, chunk_size=1000):
    total_users = len(users_data)
    total_chunks = math.ceil(total_users / chunk_size)
    
    for page_num in range(total_chunks):
        start_idx = page_num * chunk_size
        end_idx = min((page_num + 1) * chunk_size, total_users)
        chunk = users_data[start_idx:end_idx]
        
        upload_data = {
            "entityName": "users",
            "pageNumber": page_num + 1,
            "data": chunk
        }
        
        response = requests.post(
            f"{base_url}/ext/integrations/sync-sdk/v2/syncs/{sync_id}/data/snapshots",
            json=upload_data,
            headers=headers
        )
        
        if response.status_code == 200:
            print(f"Page {page_num + 1} uploaded successfully")
        else:
            print(f"Error uploading page {page_num + 1}: {response.json()}")
            # Handle error appropriately

5. Upload Data - Facts

Upload fact data (events/activities) like user activities, transactions, or contracts.

Endpoint

POST /ext/integrations/sync-sdk/v2/syncs/{syncId}/data/facts

Request Body

{
  "entityName": "user_activity",
  "pageNumber": 1,
  "data": [
    {
      "log_type": "sign_in",
      "application_name": "Zoom",
      "timestamp": "2025-06-16T09:30:00Z",
      "username": "jdoe",
      "work_email": "[email protected]",
      "ip": "192.168.1.10",
      "device": "MacBook Pro"
    }
  ]
}

Supported Entity Names for Facts

  • user_activity
  • transactions
  • contracts

6. Get Sync Details

Check the status and details of a specific sync.

Endpoint

GET /ext/integrations/sync-sdk/v2/syncs/{syncId}

Response

{
  "syncId": "684b7eb5a29ac1902134fd8e",
  "status": "started",
  "instanceId": "684b5a0953d3aba8ac97276c",
  "entitiesUploaded": {
    "users": {
      "pages": [
        {
          "page_number": 1,
          "totalRecordsUploaded": 1000,
          "uploadStatus": "finished"
        }
      ]
    }
  }
}

7. Finish Sync

Complete the sync and trigger data processing.

Endpoint

PUT /ext/integrations/sync-sdk/v2/syncs/{syncId}/status

Request Body

{
  "status": "finished"
}

Response

{
  "message": "Sync has been finished successfully and processing has started.",
  "entitiesUploaded": {
    "users": {
      "pages": [
        {
          "page_number": 1,
          "totalRecordsUploaded": 1000,
          "status": "finished"
        }
      ]
    }
  }
}

8. Cancel Sync

Abort an ongoing sync if needed.

Endpoint

DELETE /ext/integrations/sync-sdk/v2/syncs/{syncId}

Response

{
  "message": "Sync has been killed successfully"
}

9. Get Entity Schemas

Retrieve the schema definitions for all supported entities.

Endpoint

GET /ext/integrations/sync-sdk/v2/entities/schemas

This returns the complete schema for each entity, helping you understand the required and optional fields.

Code Examples

Complete Sync Flow Example (Python)

import requests
import json
from datetime import datetime

class ZluriSDKClient:
    def __init__(self, api_key):
        self.api_key = api_key
        self.base_url = "https://api.zluri.com"
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
    
    def create_instance(self, app_id, instance_name, notification_emails=None):
        """Create a new SDK instance"""
        data = {
            "appId": app_id,
            "instanceName": instance_name
        }
        if notification_emails:
            data["notificationEmails"] = notification_emails
        
        response = requests.post(
            f"{self.base_url}/ext/integrations/sync-sdk/v2/instances",
            json=data,
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()["data"]["instanceId"]
    
    def init_sync(self, instance_id):
        """Initialize a new sync"""
        response = requests.post(
            f"{self.base_url}/ext/integrations/sync-sdk/v2/instances/{instance_id}/syncs",
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()["syncId"]
    
    def upload_snapshot_data(self, sync_id, entity_name, data, page_number=1):
        """Upload snapshot data for an entity"""
        upload_data = {
            "entityName": entity_name,
            "pageNumber": page_number,
            "data": data
        }
        
        response = requests.post(
            f"{self.base_url}/ext/integrations/sync-sdk/v2/syncs/{sync_id}/data/snapshots",
            json=upload_data,
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()
    
    def finish_sync(self, sync_id):
        """Complete the sync"""
        response = requests.put(
            f"{self.base_url}/ext/integrations/sync-sdk/v2/syncs/{sync_id}/status",
            json={"status": "finished"},
            headers=self.headers
        )
        response.raise_for_status()
        return response.json()
    
    def sync_users(self, instance_id, users_data):
        """Complete flow to sync users"""
        try:
            # Step 1: Initialize sync
            sync_id = self.init_sync(instance_id)
            print(f"Sync initiated: {sync_id}")
            
            # Step 2: Upload data in chunks
            chunk_size = 1000
            for i in range(0, len(users_data), chunk_size):
                chunk = users_data[i:i+chunk_size]
                page_number = (i // chunk_size) + 1
                
                result = self.upload_snapshot_data(
                    sync_id, 
                    "users", 
                    chunk, 
                    page_number
                )
                print(f"Uploaded page {page_number}: {result['totalRecordsUploaded']} records")
            
            # Step 3: Finish sync
            finish_result = self.finish_sync(sync_id)
            print(f"Sync completed: {finish_result['message']}")
            
            return sync_id
            
        except requests.exceptions.RequestException as e:
            print(f"Error during sync: {e}")
            if hasattr(e.response, 'json'):
                print(f"Error details: {e.response.json()}")
            raise

# Example usage
if __name__ == "__main__":
    # Initialize client
    client = ZluriSDKClient("YOUR_API_KEY")
    
    # Sample user data
    users = [
        {
            "email": "[email protected]",
            "name": "John Doe",
            "department": "Engineering",
            "isActive": True,
            "jobTitle": "Software Engineer",
            "createdAt": datetime.utcnow().isoformat() + "Z"
        },
        {
            "email": "[email protected]",
            "name": "Jane Smith",
            "department": "Marketing",
            "isActive": True,
            "jobTitle": "Marketing Manager",
            "createdAt": datetime.utcnow().isoformat() + "Z"
        }
    ]
    
    # Sync users
    instance_id = "YOUR_INSTANCE_ID"
    sync_id = client.sync_users(instance_id, users)

Node.js Example

const axios = require('axios');

class ZluriSDKClient {
    constructor(apiKey) {
        this.apiKey = apiKey;
        this.baseUrl = 'https://api.zluri.com';
        this.headers = {
            'Authorization': `Bearer ${apiKey}`,
            'Content-Type': 'application/json'
        };
    }

    async createInstance(appId, instanceName, notificationEmails = []) {
        const response = await axios.post(
            `${this.baseUrl}/ext/integrations/sync-sdk/v2/instances`,
            {
                appId,
                instanceName,
                notificationEmails
            },
            { headers: this.headers }
        );
        return response.data.data.instanceId;
    }

    async initSync(instanceId) {
        const response = await axios.post(
            `${this.baseUrl}/ext/integrations/sync-sdk/v2/instances/${instanceId}/syncs`,
            {},
            { headers: this.headers }
        );
        return response.data.syncId;
    }

    async uploadSnapshotData(syncId, entityName, data, pageNumber = 1) {
        const response = await axios.post(
            `${this.baseUrl}/ext/integrations/sync-sdk/v2/syncs/${syncId}/data/snapshots`,
            {
                entityName,
                pageNumber,
                data
            },
            { headers: this.headers }
        );
        return response.data;
    }

    async finishSync(syncId) {
        const response = await axios.put(
            `${this.baseUrl}/ext/integrations/sync-sdk/v2/syncs/${syncId}/status`,
            { status: 'finished' },
            { headers: this.headers }
        );
        return response.data;
    }
}

// Example usage
async function syncUsers() {
    const client = new ZluriSDKClient('YOUR_API_KEY');
    const instanceId = 'YOUR_INSTANCE_ID';
    
    try {
        // Initialize sync
        const syncId = await client.initSync(instanceId);
        console.log(`Sync initiated: ${syncId}`);
        
        // Upload users
        const users = [
            {
                email: '[email protected]',
                name: 'Test User',
                department: 'IT',
                isActive: true
            }
        ];
        
        await client.uploadSnapshotData(syncId, 'users', users, 1);
        
        // Finish sync
        const result = await client.finishSync(syncId);
        console.log('Sync completed:', result.message);
        
    } catch (error) {
        console.error('Sync failed:', error.response?.data || error.message);
    }
}

Best Practices

1. Data Validation

  • Always validate your data against the schema before uploading
  • Use the /entities/schemas endpoint to get the latest schema definitions
  • Handle validation errors gracefully

2. Chunking Strategy

  • Keep chunks under 1000 records
  • Implement retry logic for failed chunks
  • Track page numbers to ensure no data is missed

3. Error Handling

def upload_with_retry(client, sync_id, entity_name, data, page_number, max_retries=3):
    for attempt in range(max_retries):
        try:
            return client.upload_snapshot_data(sync_id, entity_name, data, page_number)
        except requests.exceptions.RequestException as e:
            if attempt == max_retries - 1:
                raise
            print(f"Retry {attempt + 1}/{max_retries} after error: {e}")
            time.sleep(2 ** attempt)  # Exponential backoff

4. Rate Limiting

  • Respect rate limits: One sync at a time per instance
  • Syncs can be run every 6 hours
  • Implement exponential backoff for rate limit errors

5. Monitoring

  • Subscribe to notification emails for sync status
  • Use the sync details API to track progress
  • Implement logging for debugging

6. Data Consistency

  • Ensure related entities are synced together
  • For example, sync users before user activities
  • Maintain referential integrity in your data

Troubleshooting

Common Issues and Solutions

1. Authentication Errors

Error: 401 Unauthorized
Solution:

  • Verify your API key is correct
  • Ensure you're using "Bearer" prefix in the Authorization header
  • Check if the API key has been revoked

2. Schema Validation Errors

Error: 422 Schema validation failed
Solution:

  • Check the error details for specific field issues
  • Verify data types (e.g., boolean vs string)
  • Ensure required fields are present
  • Check date formats (use ISO 8601)

3. Rate Limit Errors

Error: 429 Rate limit exceeded
Solution:

  • Wait for the time specified in the Retry-At header
  • Implement proper rate limiting in your code
  • Ensure you're not running multiple syncs simultaneously

4. Sync Already Running

Error: A sync with syncId <id> is already running
Solution:

  • Complete or cancel the existing sync before starting a new one
  • Use the /syncs/{syncId} endpoint to check sync status
  • Call the DELETE endpoint to cancel if needed

5. Missing Data Dependencies

Error: Cross-entity validation failed
Solution:

  • Ensure referenced entities exist (e.g., manager email must exist in users)
  • Sync entities in the correct order
  • Verify all foreign key relationships

Debug Checklist

  • API key is valid and properly formatted
  • Instance ID is correct
  • Data conforms to the schema
  • Required fields are present
  • Data types are correct
  • Dates are in ISO 8601 format
  • Chunk size is under 1000 records
  • No duplicate sync is running
  • Rate limits are respected

Support

If you encounter issues not covered in this documentation:

  1. Check the detailed error messages in API responses
  2. Review sync logs in the Zluri dashboard
  3. Contact Zluri support at [email protected]
  4. Include your instance ID and sync ID in support requests

Appendix

Data Entity Schemas

For detailed schema information for each entity, use the GET /entities/schemas endpoint. Key entities include:

Users Schema (Key Fields)

  • email (required): User's email address
  • name: Full name
  • department: Department name
  • isActive: Account status (boolean)
  • reportingManagerEmail: Manager's email
  • createdAt: ISO 8601 datetime

User Activity Schema (Key Fields)

  • timestamp (required): Event timestamp
  • work_email (required): User's email
  • application_name: Name of the application
  • log_type: Type of activity (e.g., sign_in)

Transaction Schema (Key Fields)

  • id (required): Transaction ID
  • transaction_date (required): Transaction date
  • transaction_amount (required): Amount
  • transaction_currency (required): Currency code
  • vendor_name: Vendor name

Status Codes

CodeDescription
200Success
400Bad Request - Invalid parameters
401Unauthorized - Invalid API key
404Not Found - Resource doesn't exist
422Unprocessable Entity - Validation error
429Too Many Requests - Rate limit exceeded
500Internal Server Error

Sync Status Values

  • started: Sync is in progress
  • finished: Sync completed successfully
  • failed: Sync failed with errors
  • aborted: Sync was cancelled

Last updated: June 2025