---
title: "HR Integration"
url: "https://www.4hse.com/news/2024-03-04"
description: "We will explore how to develop software that leveraging the APIs provided by 4hse allows any HR software to integrate with 4HSE keeping personnel records data consistently synchronized. We won't focus on a specific HR software but aim for a generic approach to the problem by developing an integration script that can be easily adapted to various HR software solutions in the market."
---

Mar 4, 2024

# HR Integration

We will explore how to develop software that leveraging the APIs provided by 4hse allows any HR software to integrate with 4HSE keeping personnel records data consistently synchronized. We won't focus on a specific HR software but aim for a generic approach to the problem by developing an integration script that can be easily adapted to various HR software solutions in the market.

![HR Integration](/_astro/hero.Ch7BhWJ3_ZtCFC3.webp)

## Introduction

4HSE provides tools for managing personnel records. However, it’s becoming increasingly common for well-established companies to want to continue using their HR software and **integrate** it with a security management system like 4HSE. This challenge arises both during the project onboarding phase and in subsequent daily operational phases where maintaining a solid alignment between the two systems is crucial.

We will explore how to develop software that, leveraging the **APIs** provided by 4hse, allows any **HR software to integrate with 4HSE**, keeping personnel records data consistently synchronized.

We won’t focus on a specific HR software but aim for a generic approach to the problem by developing an integration script that can be easily adapted to various HR software solutions in the market.

## Prerequisites

What do we need to develop an HR integration script?

-   **Data source**: We will use a simple CSV file as a data source, which we imagine to be exported periodically from an HR software database to align with 4hse every 24 hours.
-   `project_id`: We need to have an active project in 4hse and know its ID. You can find the ID in the “info” section of your project window.
-   `access_token`: We need an active user with admin role for the mentioned project, as this user will be the one executing the synchronization. To authenticate this user via API, we need its access token. The token is provided by 4hse upon client request.

### Data source

To prepare the CSV file extracted from the data source, we must always consider these simple rules:

1.  The file follows a column layout predefined by 4hse.
2.  The file always contains the entire dataset of employees, updated.
3.  The field values for each individual employee in the dataset will overwrite the values for the same employee in 4hse.
4.  Employees present in 4hse but not in the source will cause the historical tracking of those present in 4hse.
5.  Employees present in the source but not in 4hse will be added to 4hse.

The CSV file consists of the following columns:

field

required

code

no

first\_name

yes

last\_name

yes

street

no

locality

no

postal\_code

no

region

no

sex

no

country

no

birth\_date

no

birth\_place

no

tax\_code

no

note

no

is\_employee

yes

Among these fields, it is necessary to identify a key field, i.e., a field whose value uniquely identifies an employee. It can be any of these fields, usually the `code` or the tax code `tax_code`.

### 4HSE REST API

4hse provides a standard OpenAPI REST API for executing and monitoring synchronization. The `create` endpoint receives a request containing an input CSV and creates a task at the 4hse processing servers. The service responds immediately upon successful task creation, providing the unique task identifier. The task processing will be executed as soon as possible. The `view` endpoint provides the processing status of the task identified by its ID.

Complete documentation of the APIs can be found here: [PeopleSync API](https://docs.4hse.com/it/api/peoplesync/).

#### Example

In this example, we want to establish periodic synchronization between the data extracted from our HR and the “My Company” project in 4hse. Suppose the CSV file `employees-1.csv` has been extracted from our HR software and appropriately transformed to follow the CSV column structure provided by 4hse. The file contains 7 employees.

code

first\_name

last\_name

street

locality

postal\_code

region

sex

country

birth\_date

birth\_place

tax\_code

note

is\_employee

1

John

Doe

123 Main St

Anytown

12345

CA

M

USA

1980-05-15

Anytown

ABC123456

Note for John Doe

1

2

Jane

Smith

456 Oak St

Someville

67890

NY

F

USA

1985-08-21

Someville

DEF789012

Note for Jane Smith

1

3

Robert

Johnson

789 Pine St

Another City

54321

TX

M

USA

1972-11-03

Another City

GHI345678

Note for Robert Johnson

1

4

Emily

Davis

101 Elm St

Yourtown

98765

FL

F

USA

1992-04-18

Yourtown

JKL901234

Note for Emily Davis

1

5

Michael

Miller

202 Cedar St

Cityville

13579

IL

M

USA

1988-07-27

Cityville

MNO567890

Note for Michael Miller

1

6

Sophia

Brown

303 Maple St

Smalltown

24680

GA

F

USA

1995-02-09

Smalltown

PQR123456

Note for Sophia Brown

1

7

Daniel

Wilson

404 Birch St

Newtown

86420

WA

M

USA

1983-09-12

Newtown

STU789012

Note for Daniel Wilson

1

-   `ACCESS_TOKEN`: It is the token that identifies the 4hse user who will perform the operation, an admin of the project.
-   `PROJECT_ID`: It is the ID of the project we have just created.
-   `PRIMARY_KEY`: It is the column in the CSV that uniquely identifies an employee. We have chosen “tax\_code.”
-   `EMULATION`: It allows us to execute the request in emulation mode. This means we can perform the sync and obtain the results without actually making the changes. This allows us to test the outcome of the operation before applying it. We will initially set this value to `TRUE`.
-   `MAX_CHANGES`: It is a security parameter that controls the maximum number of changes applicable in the request. If the total number of changes made exceeds this value, we will be alerted, and the request will be rejected. We initially set it to `150`.

Now let’s define the `createTask` method, which will be used to create the synchronization task at the `people-sync` service of 4hse.  
The creation of the task is a simple `HTTP POST` request to the server, which responds with a `task_id`.  
The task ID is an identifier for the task that allows us to query the server to know its progress (queued, executed, etc.).

The method only requires the input of the file name to synchronize and provides the output of the created task ID.

```
private static String createTask(String filePath) throws IOException {
    ...
}
```

The first thing to do is to instantiate an HTTP client for sending the request:

```
private static String createTask(String filePath) throws IOException {
    HttpClient httpClient = HttpClients.createDefault();
    HttpPost httpPost = new HttpPost("http://localhost:8081/people-sync/create");
}
```

Since we need to send a file, the data must be transmitted in multipart format.  
Let’s populate the body of the multipart request by providing the file and the remaining input parameters:

```
private static String createTask(String filePath) throws IOException {
    HttpClient httpClient = HttpClients.createDefault();
    HttpPost httpPost = new HttpPost("http://localhost:8081/people-sync/create");

    MultipartEntityBuilder builder = MultipartEntityBuilder.create();

    // Add file parameter
    builder.addBinaryBody("file", new File(filePath),
        ContentType.APPLICATION_OCTET_STREAM, "file.csv");

    // Add other parameters
    builder.addTextBody("access-token", ACCESS_TOKEN);
    builder.addTextBody("project_id", PROJECT_ID);
    builder.addTextBody("pk", PRIMARY_KEY);
    builder.addTextBody("emulation", String.valueOf(EMULATION));
    builder.addTextBody("max_changes", String.valueOf(MAX_CHANGES));

    HttpEntity multipart = builder.build();
    httpPost.setEntity(multipart);
}
```

Finally, we execute the request.  
In case of success, the server will respond with a JSON that needs to be parsed to obtain the `task_id`, which we will return as output.  
In case of failure, we print the error on the screen.

```
private static String createTask(String filePath) throws IOException {
    HttpClient httpClient = HttpClients.createDefault();
    HttpPost httpPost = new HttpPost("http://localhost:8081/people-sync/create");

    MultipartEntityBuilder builder = MultipartEntityBuilder.create();

    // Add file parameter
    builder.addBinaryBody("file", new File(filePath), 
    ContentType.APPLICATION_OCTET_STREAM,"file.csv");

    // Add other parameters
    builder.addTextBody("access-token", ACCESS_TOKEN);
    builder.addTextBody("project_id", PROJECT_ID);
    builder.addTextBody("pk", PRIMARY_KEY);
    builder.addTextBody("emulation", String.valueOf(EMULATION));
    builder.addTextBody("max_changes", String.valueOf(MAX_CHANGES));

    HttpEntity multipart = builder.build();
    httpPost.setEntity(multipart);

    // Execute the request
    HttpResponse response = httpClient.execute(httpPost);

    // Parse the response JSON
    JSONParser parser = new JSONParser();
    try (InputStream content = response.getEntity().getContent()) {
        JSONObject jsonResponse = (JSONObject) parser.parse(new InputStreamReader(content));
        return (String) jsonResponse.get("task_id");
    } catch (Exception e) {
        e.printStackTrace();
        return null;
    }
}
```

Now, let’s define a method `readTask` that, using the `task_id`, will obtain the current status of the task and print it on the screen.

```
    private static void readTask(String taskId) throws IOException {
    ...
    }
```

Similarly to the previous method, we instantiate an HTTP client that, this time, makes a request using the `GET method`, providing the `task_id and the` access-token\` for user identification.

```
    private static void readTask(String taskId) throws IOException {
        HttpClient httpClient = HttpClients.createDefault();
        HttpGet httpGet = new HttpGet("http://localhost:8081/people-sync/view?id=" + taskId + "&access-token=" + ACCESS_TOKEN);
    }
```

We execute the request, parse the JSON response, and print the `task_id` on the screen.

```
private static void readTask(String taskId) throws IOException {
    HttpClient httpClient = HttpClients.createDefault();
    HttpGet httpGet = new HttpGet("http://localhost:8081/people-sync/view?id=" + taskId + "&access-token=" + ACCESS_TOKEN);

    // Execute the request
    HttpResponse response = httpClient.execute(httpGet);

    // Parse and print the response JSON
    JSONParser parser = new JSONParser();
    try (InputStream content = response.getEntity().getContent()) {
        JSONObject jsonResponse = (JSONObject) parser.parse(new InputStreamReader(content));
        System.out.println("Task: " + jsonResponse.toJSONString());
    } catch (Exception e) {
        e.printStackTrace();
    }
}
```

Finally, we define the main method, which will be the entry point of our script. The available commands for the script will be:

Ecco la conversione in Markdown:

-   `create` to create the task
    
    ```
    java -jar your-jar-file.jar create filename.csv
    ```
    
-   `read` to read the task
    
    ```
    java -jar your-jar-file.jar read task-id
    ```
    

We then check that the input parameters are provided; otherwise, we display a message on the screen.

```
public static void main(String[] args) {
    String command = args[0]; //create or read

    if (args.length < 2) {
        System.out.println("Usage:");
        System.out.println("java -jar your-jar-file.jar create file.csv");
        System.out.println("java -jar your-jar-file.jar read task-id");
        return;
    }
}
```

We then check if the first parameter passed to the command is `create` or `read` and call the respective methods, passing the CSV file name as an argument for creation or the task ID for reading.

```
    public static void main(String[] args) {
        String command = args[0]; //create or read

        if (args.length < 2) {
            System.out.println("Usage:");
            System.out.println("java -jar your-jar-file.jar create file.csv");
            System.out.println("java -jar your-jar-file.jar read task-id");
            return;
        }

        try {
            if ("create".equals(args[0])) {
            String taskId = createTask(args[1]);
            System.out.println("Created Task ID: " + taskId);
            }
            else if ("read".equals(args[0])) {
                readTask(args[1]);
            }
            else {
                System.out.println("Action must be 'create' or 'read'");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
```

Now let’s run the script by initiating the task creation with the command:

```
java -jar target/my-java-project-1.0-SNAPSHOT-jar-with-dependencies.jar 
create employees2-1.csv
```

In response, we obtain the ID of the created task:

```
Created Task ID: be9eec92-621c-31b1-a525-7df391f599bf
```

After waiting for a few seconds, we execute the command to read the status of the created task:

```
java -jar target/my-java-project-1.0-SNAPSHOT-jar-with-dependencies.jar 
read be9eec92-621c-31b1-a525-7df391f599bf
```

In response, we get the complete report of the server’s actions:

-   `task_id`: task ID
-   `entity_id`: project ID
-   `user_id`: the user who created the task
-   `status`: indicates whether the task was executed successfully or not
-   `created_at`, `updated_at`: provide temporal references to the creation and update of the task
-   `response.result`: is a history of operations performed for each record in our CSV file. In this case, as they were new employees, each employee, identified by their `tax_code`, was added to the project.
-   `response.changes`: is a summary of all operations performed. In this case, the task made a total of 7 changes, all of which were “additions.”
-   `log`: the server log recorded during the task execution. It is useful for identifying problems in case of errors.

```
{
    "task_id": "a386a5bf-5bc4-3be7-9392-c81c69918bfc",
    "entity_id": "41ec7395-eeda-4c8e-bc73-5585d48e5161",
    "user_id": "john@4hse.com",
    "status": "DONE",
    "created_at": "2024-01-11 08:14:53",
    "updated_at": "2024-01-11 08:15:07",
    "response": {
        "result": {
            "ABC123456": [
                {
                    "action": "add",
                    "id": "ABC123456"
                }
            ],
            "PQR123456": [
                {
                    "action": "add",
                    "id": "PQR123456"
                }
            ],
            "DEF789012": [
                {
                    "action": "add",
                    "id": "DEF789012"
                }
            ],
            "STU789012": [
                {
                    "action": "add",
                    "id": "STU789012"
                }
            ],
            "MNO567890": [
                {
                    "action": "add",
                    "id": "MNO567890"
                }
            ],
            "GHI345678": [
                {
                    "action": "add",
                    "id": "GHI345678"
                }
            ],
            "JKL901234": [
                {
                    "action": "add",
                    "id": "JKL901234"
                }
            ]
        },
        "log": [
            {
                "level": "INFO",
                "text": "Task a386a5bf-5bc4-3be7-9392-c81c69918bfc executed",
                "timestamp": 1704960907
            },
            {
                "level": "INFO",
                "text": "Executing task a386a5bf-5bc4-3be7-9392-c81c69918bfc in emulation mode",
                "timestamp": 1704960906
            }
        ],
        "changes": {
            "add": 7,
            "total": 7,
            "activate": 0,
            "update": 0,
            "deactivate": 0
        }
    }
}
```

At this point, let’s change the emulation parameter to `false` and rerun the create command.

```
private final static boolean EMULATION = false;
```

We will receive the same response as before, but in this case, the changes have been actually executed, and therefore, we will have 7 employees in the “La mia azienda” project.

Now, let’s assume that in the company, 2 people have retired. Consequently, our HR has created a new file `employees2-2.csv` containing only the remaining 5 individuals. Additionally, for “John Doe” the `NOTE` field has been modified. In total, there are 3 differences compared to the `employees2-1.csv` file.

code

first\_name

last\_name

street

locality

postal\_code

region

sex

country

birth\_date

birth\_place

tax\_code

note

is\_employee

1

John

Doe

123 Main St

Anytown

12345

CA

M

USA

1980-05-15

Anytown

ABC123456

Note for John Doe

1

2

Jane

Smith

456 Oak St

Someville

67890

NY

F

USA

1985-08-21

Someville

DEF789012

Note for Jane Smith

1

3

Robert

Johnson

789 Pine St

Another City

54321

TX

M

USA

1972-11-03

Another City

GHI345678

Note for Robert Johnson

1

4

Emily

Davis

101 Elm St

Yourtown

98765

FL

F

USA

1992-04-18

Yourtown

JKL901234

Note for Emily Davis

1

5

Michael

Miller

202 Cedar St

Cityville

13579

IL

M

USA

1988-07-27

Cityville

MNO567890

Note for Michael Miller

1

Let’s modify our script, specifically the `MAX_CHANGES` parameter, reducing it to 2.

```
private final static int MAX_CHANGES = 2;
```

Let’s run the create command again, this time sending the `employees2-2.csv` file:

```
java -jar target/my-java-project-1.0-SNAPSHOT-jar-with-dependencies.jar create 
employees2-2.csv
```

Read the status of the newly created task, and we encounter an error as the actual number of changes (3) exceeds the maximum allowed by this request (2).

```
{
    "task_id": "b9819d5e-a0a7-34e0-844d-cde54ed91858",
    "entity_id": "41ec7395-eeda-4c8e-bc73-5585d48e5161",
    "user_id": "john@4hse.com",
    "status": "FAILED",
    "created_at": "2024-01-11 09:19:03",
    "updated_at": "2024-01-11 09:19:21",
    "response": {
        "log": [
            {
                "level": "ERROR",
                "text": "Out of max changes",
                "timestamp": 1704964761
            }
        ]
    }
}
```

Let’s modify the value of `MAX_CHANGES` again, setting it to `150`.

```
private final static int MAX_CHANGES = 150;
```

Let’s rerun the task creation command and check its status:

```
{
    "task_id": "b7ede071-e5c2-37c9-8075-9cd1b5580f26",
    "entity_id": "41ec7395-eeda-4c8e-bc73-5585d48e5161",
    "user_id": "john@4hse.com",
    "created_at": "2024-01-11 08:22:36",
    "updated_at": "2024-01-11 08:23:16",
    "status": "DONE",
    "response": {
        "result": {
            "ABC123456": [
                {
                    "changes": {
                        "note": "Another note for John Doe"
                    },
                    "action": "update",
                    "id": "ABC123456"
                }
            ],
            "PQR123456": [
                {
                    "action": "deactivate",
                    "periods": [
                        {
                            "end_date": "2024-01-11 00:00:00",
                            "start_date": "1970-01-01 00:00:00"
                        }
                    ],
                    "id": "PQR123456"
                }
            ],
            "STU789012": [
                {
                    "action": "deactivate",
                    "periods": [
                        {
                            "end_date": "2024-01-11 00:00:00",
                            "start_date": "1970-01-01 00:00:00"
                        }
                    ],
                    "id": "STU789012"
                }
            ]
        },
        "log": [
            {
                "level": "INFO",
                "text": "Task b7ede071-e5c2-37c9-8075-9cd1b5580f26 executed",
                "timestamp": 1704961396
            },
            {
                "level": "INFO",
                "text": "Executing task b7ede071-e5c2-37c9-8075-9cd1b5580f26 in emulation mode",
                "timestamp": 1704961396
            }
        ],
        "changes": {
            "add": 0,
            "total": 3,
            "activate": 0,
            "update": 1,
            "deactivate": 2
        }
    }
}
```

The response shows the three expected changes. The two individuals who retired have been historically recorded, and the notes for the relevant employee have been modified.

## Conclusions

We have seen how easy it is to synchronize your HR database with 4hse, keeping both systems perfectly aligned. In the next tutorial, we will further extend this example script to leverage more in-depth synchronization mechanisms.

Sources: [https://github.com/4hse/example-people-sync](https://github.com/4hse/example-people-sync)