> ## Documentation Index
> Fetch the complete documentation index at: https://docs.twine.se/llms.txt
> Use this file to discover all available pages before exploring further.

# Data Model

> How Twine models entities and tracks property values over time

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.

<CardGroup cols={2}>
  <Card title="Employee" icon="user" href="/platform/data-model/employee">
    The primary entity. Covers identity, employment, compensation, organisational structure, and more.
  </Card>

  <Card title="Organizational Unit" icon="sitemap" href="/platform/data-model/organizational-unit">
    Nodes in a company's structure - departments, divisions, teams, and similar.
  </Card>

  <Card title="Project" icon="diagram-project" href="/platform/data-model/project">
    Project entities. Supported in Twine but not yet available through the public API.
  </Card>

  <Card title="Customer" icon="building" href="/platform/data-model/customer">
    Customer entities. Supported in Twine but not yet available through the public API.
  </Card>

  <Card title="Competence" icon="award" href="/platform/data-model/competence">
    Skills, certificates and licences, and the assignments that give them to employees.
  </Card>
</CardGroup>

## 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.

<Warning>
  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.
</Warning>

Each dated property has three fields:

| Field        | Type           | Description                                                                                                                                                                                                                                                                                                                                                              |
| ------------ | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `valid_from` | date or null   | The date from which this value is effective. `null` means the value has been valid since the beginning of time - used for properties where history is not meaningful, such as `first_name`.                                                                                                                                                                              |
| `value`      | any or null    | The value itself. A `null` value signals that the property is no longer active from `valid_from` onwards - for example, a `salary_amount` entry with `value: null` and `valid_from: 2021-01-01` means the salary ended on 2020-12-31.                                                                                                                                    |
| `id`         | string or null | A discriminator for properties that can have multiple simultaneous values. `null` by default. Used when a single property can hold several independent values at the same point in time - such as different benefit types. The `id` is derived from the source system (often an external ID or a combination of fields), or can be set explicitly using the Data Engine. |

### Example

A `salary_amount` history might look like this:

```json theme={null}
[
  { "valid_from": "2024-01-01", "value": 55000, "id": null },
  { "valid_from": "2022-06-01", "value": 50000, "id": null },
  { "valid_from": null,         "value": 45000, "id": null }
]
```

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:

```json theme={null}
[
  { "valid_from": "2021-01-01", "value": null,  "id": null },
  { "valid_from": "2019-03-01", "value": 50000, "id": null }
]
```

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:

```json theme={null}
[
  { "valid_from": "2023-01-01", "value": "premium",  "id": "benefit_001" },
  { "valid_from": "2023-01-01", "value": "standard", "id": "benefit_002" },
  { "valid_from": "2021-06-01", "value": "basic",    "id": "benefit_001" }
]
```

### 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.

<Note>
  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`.
</Note>

<Card title="Dated property patterns" icon="code" href="/platform/data-model/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.
</Card>

## 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` and `source_organizational_unit_external_ids`) are internal tracking fields that Twine uses to maintain references back to records in the originating system. They are exposed in the API but should not be relied upon for business logic if a more semantically meaningful property exists. Use them only when no suitable alternative is available.

The most important source property is `source_external_id`. It must always be mapped to the primary key of the entity in the remote system. Twine relies on it to identify and match records across syncs, and computed properties like `manager_id` depend on it being present and correct.
