Your First People Integration

Your First People Integration

What you’ll learn: How to onboard an employee end to end – create a person under your own identifier, address that person without ever storing a Protime ID, assign them to a department, give them a contract, update their details, and track changes with delta.

Prerequisites

Before we begin, make sure you have the following:

  • A tenant name for your Protime environment (e.g. acme from https://acme.myprotime.eu)
  • An OAuth2 client_id and client_secret provided by Protime
  • A tool for making HTTP requests (e.g. curl, Postman, or your programming language of choice)
  • The department ID of a department in your Protime environment (we’ll use 825 in this tutorial)
  • Completed the Your first API call tutorial

Step 1: Authenticate with people and contract scopes

We need read and write permissions for both people and contracts. The connector-protimeapi-people.write scope also covers the historical field endpoints we use in Step 4.

Request

POST
https://authentication.<environmentURL>/tenants/<tenantName>/connect/token

POST /tenants/acme/connect/token HTTP/1.1
Host: authentication.myprotime.eu
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials&client_id=your-client-id&client_secret=your-client-secret&scope=connector-protimeapi-people.read connector-protimeapi-people.write connector-protimeapi-contracts.read connector-protimeapi-contracts.write

Verify: The response contains an access_token with all four scopes listed.

{
  "access_token": "eyJ...Uc",
  "expires_in": 1800,
  "token_type": "Bearer",
  "scope": "connector-protimeapi-people.read connector-protimeapi-people.write connector-protimeapi-contracts.read connector-protimeapi-contracts.write"
}

We’ll use this token for all remaining steps.

Step 2: Create a person with your own identifier

Let’s hire John Doe. Instead of creating him and then looking up the Protime ID, we attach our own HR identifier in the same call using the externalReferenceIdentifier property. From that moment on we can address him as Emp123.

Request

POST
https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/people

POST /connector/protimeapi/api/v1/people HTTP/1.1
Host: acme.myprotime.eu
Authorization: Bearer eyJ...Uc
Content-Type: application/json
{
  "firstName": "John",
  "lastName": "Doe",
  "email": "john.doe@acme.com",
  "town": "Brussels",
  "countryISOCode": "BE",
  "badgeNumber": "6767676",
  "employeeNumber": "E-00421",
  "inServiceDate": "2026-09-01",
  "externalReferenceIdentifier": {
    "HRMID": "Emp123"
  }
}

Verify: You should see a 201 Created response. The Location header contains the URL of the new person, and the body is empty.

HTTP/1.1 201 Created
Location: /connector/protimeapi/api/v1/people/8526

firstName, lastName and inServiceDate are the only required fields. The externalReferenceIdentifier holds exactly one key-value pair.

Note

HRMID is a custom reference name, so the pair is stored and becomes reusable. Had we used a predefined key (@badge-number or @employee-number), nothing would be stored – those two always resolve from the badgeNumber and employeeNumber fields, and the entry would act purely as a duplicate guard. If another person already carries HRMID Emp123, the request fails with 409 Conflict.

Step 3: Retrieve the person by your identifier

This is the payoff. We never saved the ID 8526 – we can fetch John with the identifier our own system already knows, by naming the collection and reference in the externalReferences query parameter.

Request

GET
https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/people/Emp123?externalReferences=(people,HRMID)

GET /connector/protimeapi/api/v1/people/Emp123?externalReferences=(people,HRMID) HTTP/1.1
Host: acme.myprotime.eu
Authorization: Bearer eyJ...Uc

Verify: The response is John’s record. Note the id – the API resolved Emp123 to person 8526 for us.

{
  "id": 8526,
  "changeVersion": "0001E7C5000098FA0013",
  "lastName": "Doe",
  "firstName": "John",
  "email": "john.doe@acme.com",
  "town": "Brussels",
  "countryISOCode": "BE",
  "inServiceDate": "2026-09-01",
  "employeeNumber": "E-00421",
  "badgeNumber": "6767676"
}

Note

The person resource itself does not echo an externalReferences object – here the reference only selects which person to return. References are echoed when a person appears as a nested property of another resource, as we’ll see in Step 5.

Because John has a badge number and an employee number, these two calls reach the same record:

GET /connector/protimeapi/api/v1/people/6767676?externalReferences=(people,@badge-number)
GET /connector/protimeapi/api/v1/people/E-00421?externalReferences=(people,@employee-number)

Step 4: Assign the person to a department

Organizational assignments are tracked over time, so a department is assigned from a date. This endpoint uses two references at once: the query parameter selects the person, and the request body points at the department.

Request

POST
https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/people/Emp123/department-history?externalReferences=(people,HRMID)

POST /connector/protimeapi/api/v1/people/Emp123/department-history?externalReferences=(people,HRMID) HTTP/1.1
Host: acme.myprotime.eu
Authorization: Bearer eyJ...Uc
Content-Type: application/json
{
  "department": {
    "id": 825
  },
  "from": "2026-09-01"
}

Verify: You should see a 201 Created response with an empty body. Unlike Step 2, this endpoint returns no Location header.

The department can also be addressed by a custom reference instead of its ID, provided that reference is already registered. Departments have no predefined references:

{
  "department": {
    "externalReferences": {
      "customDepartmentCode": "PROD-01"
    }
  },
  "from": "2026-09-01"
}

Let’s read the history back, asking for the person reference to be enriched:

Request

GET
https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/people/Emp123/department-history?externalReferences=(people,HRMID)

{
  "value": [
    {
      "person": {
        "id": 8526,
        "externalReferences": {
          "HRMID": "Emp123"
        }
      },
      "id": 41207,
      "changeVersion": "0001E7C5000098FA0021",
      "from": "2026-09-01",
      "department": {
        "id": 825
      }
    }
  ]
}

until is absent because the assignment is still open. Assigning a later department closes this entry automatically.

The same pattern works for employer-history, job-history, job-category-history, sector-history and work-location-history.

Step 5: Create a contract

A contract records the employment terms. Here the person is identified from the request body, because this endpoint has no route parameter to resolve.

Request

POST
https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/contracts

POST /connector/protimeapi/api/v1/contracts HTTP/1.1
Host: acme.myprotime.eu
Authorization: Bearer eyJ...Uc
Content-Type: application/json
{
  "person": {
    "externalReferences": {
      "HRMID": "Emp123"
    }
  },
  "code": "CONTRACT-2026",
  "from": "2026-09-01",
  "contractHoursInMinutes": 2280,
  "fullTimeEquivalentInMinutes": 2400,
  "numberOfWorkingDaysPerWeek": 5,
  "numberOfDaysInContractPeriod": 7,
  "kind": "Contract"
}

Verify: You should see a 201 Created response with a Location header.

HTTP/1.1 201 Created
Location: /connector/protimeapi/api/v1/contracts/186

numberOfDaysInContractPeriod must be a multiple of 7 between 7 and 364. Both minute values must be between 1 and 5999.

Now fetch the contract and ask for the person’s reference to be included:

Request

GET
https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/contracts/186?externalReferences=(people,HRMID)

Verify: The nested person object carries our identifier alongside the Protime ID, so the contract can be matched to an employee in our own system without a lookup table.

{
  "changeVersion": "0001E7C5000098FA0030",
  "id": 186,
  "contractPercentage": 95.0,
  "person": {
    "id": 8526,
    "externalReferences": {
      "HRMID": "Emp123"
    }
  },
  "code": "CONTRACT-2026",
  "from": "2026-09-01",
  "contractHoursInMinutes": 2280,
  "fullTimeEquivalentInMinutes": 2400,
  "numberOfWorkingDaysPerWeek": 5,
  "numberOfDaysInContractPeriod": 7,
  "kind": "Contract"
}

contractPercentage is returned by Protime and is not part of the request body – you cannot set it.

Step 6: Update the person

John moved house. We address him by the same identifier, this time on a PUT.

Request

PUT
https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/people/Emp123?externalReferences=(people,HRMID)

PUT /connector/protimeapi/api/v1/people/Emp123?externalReferences=(people,HRMID) HTTP/1.1
Host: acme.myprotime.eu
Authorization: Bearer eyJ...Uc
Content-Type: application/json
{
  "firstName": "John",
  "lastName": "Doe",
  "email": "john.doe@acme.com",
  "address": "Main Street 1",
  "postalCode": "2800",
  "town": "Mechelen",
  "countryISOCode": "BE",
  "badgeNumber": "6767676",
  "employeeNumber": "E-00421",
  "inServiceDate": "2026-09-01"
}

Verify: You should see a 200 OK response with an empty body.

Caution

PUT replaces the person’s data rather than patching it. firstName, lastName and inServiceDate are required on every call, and any optional field you leave out is sent as empty. Always send the complete object.

Note the two differences from Step 2: the reference moved from the body to the query parameter, and externalReferenceIdentifier is ignored here. To change a reference on an existing person, use the external references endpoints.

Step 7: Track changes with delta

Rather than re-reading every person on each sync, delta returns only what changed. We start one by adding the delta query parameter to the list endpoint.

Request

GET
https://<tenant>.myprotime.eu/connector/protimeapi/api/v1/people?delta

GET /connector/protimeapi/api/v1/people?delta HTTP/1.1
Host: acme.myprotime.eu
Authorization: Bearer eyJ...Uc

Unlike the clockings delta, the people delta takes no filter – this endpoint accepts none.

Verify: The response contains a value array with the current people. If there is a nextLink, we must follow it to page through all data.

{
  "value": [
    {
      "id": 8526,
      "changeVersion": "0001E7C5000098FA0042",
      "lastName": "Doe",
      "firstName": "John",
      "town": "Mechelen",
      "inServiceDate": "2026-09-01"
    }
  ],
  "nextLink": "/connector/protimeapi/api/v1/people?continuationToken=eyJ2YW...&deltaToken=eyJkZW..."
}

We follow every nextLink until we reach a page that carries a deltaLink instead. That final page signals we have consumed all initial data.

{
  "value": [],
  "deltaLink": "/connector/protimeapi/api/v1/delta/people?deltaToken=eyJkZWx0YUlkIjo2NzcsInN0YXJ0aW5nQ3Vyc29yIjowLCJ0aW1lU3RhbXAiOiIyMDI2LTA5LTAxVDEwOjAwOjAwLjAwMDAwMDBaIn0="
}

Save that deltaLink – it is our bookmark. The initial pages already reflect John’s updated town, so nothing is outstanding right now. The next time his record changes – say a colleague adds his telephone number – polling the bookmark returns just that:

{
  "value": [
    {
      "changeType": "InsertOrUpdate",
      "data": {
        "id": 8526,
        "changeVersion": "0001E7C5000098FA0055",
        "lastName": "Doe",
        "firstName": "John",
        "telephone": "+32 2 123 45 67",
        "town": "Mechelen",
        "inServiceDate": "2026-09-01"
      }
    }
  ],
  "deltaLink": "/connector/protimeapi/api/v1/delta/people?deltaToken=eyJkZWx0YUlkIjo2NzcsInN0YXJ0aW5nQ3Vyc29yIjoxMjM0LCJ0aW1lU3RhbXAiOiIyMDI2LTA5LTAxVDEwOjA1OjAwLjAwMDAwMDBaIn0="
}

Each change carries a changeType of InsertOrUpdate or Delete, and a data object with the full record. Each response replaces the previous deltaLink – store the new one for your next poll.

Caution

A delta expires after 72 hours. Call the deltaLink at least once within that window, even when you expect no changes. If it expires, the API returns 410 Gone and you must reinitialize.

What you’ve accomplished

  • Authenticated with people and contract scopes
  • Created a person and registered your own identifier in the same call
  • Retrieved, assigned, contracted and updated that person without ever storing a Protime ID
  • Learned the three places a reference can appear: the externalReferenceIdentifier body property on create, the externalReferences query parameter on a route, and a nested externalReferences object pointing at a related resource
  • Initialized delta tracking and learned how later changes surface as InsertOrUpdate events

Next steps

Related concepts