Skip to main content
Twine’s data model is built around a small set of core entities - Employee, Organizational Unit, Project, Customer, and Competence. Each entity carries a set of properties that describe it. The defining characteristic of these properties is that they are date-tracked: rather than storing only the current value, Twine records values by when they take effect. The depth of history available depends on what the source system exposes - refer to the relevant integration page for details.

Employee

The primary entity. Covers identity, employment, compensation, organisational structure, and more.

Organizational Unit

Nodes in a company’s structure - departments, divisions, teams, and similar.

Project

Project entities. Supported in Twine but not yet available through the public API.

Customer

Customer entities. Supported in Twine but not yet available through the public API.

Competence

Skills, certificates and licences, and the assignments that give them to employees.

Dated Properties

Every property on a Twine entity is represented as an array of Dated Properties, sorted in descending order by valid_from. This means the most recently effective value is always first in the list.
The first entry is the latest value by effective date - not necessarily the current value. A property can have a future-dated entry at the top of the list if a change has already been recorded but has not yet taken effect.
Each dated property has three fields:

Example

A salary_amount history might look like this:
The first entry is the most recently effective salary. If today is before 2024-01-01, the current salary is the 50 000 entry - but 55 000 still appears first in the list. A terminated salary would look like this:
The null value entry on 2021-01-01 signals that the salary ended on 2020-12-31.

Properties with multiple simultaneous values

When a property can hold more than one value at the same time - such as cost centre allocations or benefits - the id field segments them. Each distinct id represents an independent value series, with its own history:

List properties

Some properties hold an array as their value rather than a scalar - for example, team_id, which lists all teams an employee belongs to. These behave identically to scalar dated properties, except that value is an array.
A rework of how certain list properties are modelled is currently being evaluated. This may affect how values are stored and tracked for properties such as team_id.

Dated property patterns

A compact string format for encoding multiple date-tracked values into a single text field - used when a source system cannot represent dated values natively.

Custom Properties

Every entity supports a custom_properties map. Each key in the map is a property name defined by the customer, and its value is an array of dated properties following the same structure as built-in properties - including support for the id discriminator. Custom properties are used when Twine’s built-in property set does not cover a field that a customer needs to transfer. They behave identically to built-in properties in the Data Engine and in domain mappings.

Computed Properties

Some properties are automatically derived from other properties unless you explicitly map them yourself. For example, manager_id is resolved from source_external_manager_id - Twine looks up the employee whose source_external_id matches the value in source_external_manager_id and populates manager_id with their Twine UUID. If you provide an explicit mapping for a computed property, the automatic derivation is skipped entirely and your mapping takes precedence.

Source Properties

Properties prefixed with source_ (such as source_external_id, source_external_manager_id, source_organizational_unit_external_ids and, on organizational units, source_external_parent_id) are tracking fields that Twine uses during a sync to link the records it is processing back to the originating system. They are exposed in the API, but they are not a stable view of the data.
Do not build on source_* properties. They hold whatever the last integration that wrote the record put there. As soon as an employee or organizational unit is fed by more than one integration (an HR system and a scheduling system, say, or an HR system and a payroll system that reports back), every sync overwrites them with that integration’s ids. The value you read depends on which sync ran most recently, and it changes as soon as another one runs. The same applies to the top-level source_system field on employees.
Every source_* property has a stable counterpart. Use these instead: manager_id and the tier id properties are computed, and computed values are stable across integrations. If a mapping sets one of them explicitly, the explicit mapping decides what it holds, so check the mapping before relying on it. The mapping relations are always stable. source_external_id still matters on the way in: every domain mapping must map it to the primary key of the entity in the remote system. Twine uses it to identify and match records within that sync, and computed properties like manager_id depend on it being present and correct. It is the reading side, outside the sync, that is unreliable.