LUSID has a set of built-in entities that represent real-world objects central to the task of managing investment data, such as portfolios, instruments, quotes and so on.
You can create a custom entity to model a real-world object or concept that LUSID does not natively represent, for example corporate office location. Note the following:
A custom entity must have a custom entity type that specifies values for two built-in data fields,
displayName
anddescription
, and defines user-specified data fields. Once created, you can modify a custom entity type, but only with caveats.A custom entity must have at least one identifier. Under the hood, each identifier is defined as a property with a
constraintStyle
ofIdentifier
and a 3-stage property key in theCustomEntity
domain.You can create relationships between a custom entity and certain other types of entity, for example between a corporate office location and the person entities allowed to work there.
You can apply access metadata to a custom entity. This may be a more effective way of restricting access to custom entity data stored in LUSID.
LUSID stores custom entity data bitemporally and you can retrieve historical information by rolling back the as at timeline in the normal way. However, LUSID does not know how to interpret your custom entity data. It cannot be used to inform holding calculations or valuation operations.
Custom entities are not feature-complete yet.
Providing you have appropriate access control permissions, you can interact with a custom entity (and any relationships it might have):
Using the LUSID API endpoints in the Custom Entity and Custom Entity Types collections.
If you have a Luminesce license, using read and write providers you create yourself.
Defining a custom entity type
The first task is to create a custom entity type defining core characteristics. You can then create as many custom entities of this type as you need.
To do this, obtain an API token and call the CreateCustomEntityType API for your LUSID domain, specifying a unique name for the type, values for the built-in data fields displayName
and description
, and defining user-specified data fields. Note LUSID automatically prefixes the entity type name with ~
to distinguish it from current or future built-in entity types.
User-specified data fields are analogous to, though not the same as, properties. Each can:
Be
required
or not.Have a
lifeTime
ofPerpetual
orTimeVariant
(that is, expected to vary during different time periods). More information.Have a data
type
of eitherString
,Boolean
,DateTime
orDecimal
.Have a
collectionType
ofArray
to store multiple values (note that array values are ordered and may contain duplicates).
Note: Unlike properties, user-specified data fields cannot be scoped (that is, entitled) independently of parent custom entities.
For example, to create a custom entity type to represent the concept of corporate office location:
curl -X POST "https://<your-domain>.lusid.com/api/api/customentitytypes"
-H "Authorization: Bearer <your-API-access-token>"
-H "Content-Type: application/json-patch+json"
-d '{
"entityTypeName": "Office",
"displayName": "Office location",
"description": "An office or branch location",
"fieldSchema": [
{
"name": "address",
"lifetime": "Perpetual",
"type": "String",
"required": false,
"description": "The address of the location"
},
{
"name": "seatingCapacity",
"lifetime": "TimeVariant",
"type": "Decimal",
"required": false,
"description": "The seating capacity of the location"
},
{
"name": "isHeadOffice",
"lifetime": "TimeVariant",
"type": "Boolean",
"required": true,
"description": "Whether or not the location is a head office"
},
{
"name": "Amenities",
"lifetime": "TimeVariant",
"type": "String",
"collectionType": "Array",
"required": false,
"description": "A list of facilities for staff"
}
]
}'
Providing the request is successful, the response identifies the entity type as ~Office
:
{
"entityTypeName": "Office",
"displayName": "Office location",
"description": "An office or branch location",
"entityType": "~Office",
"fieldSchema": [
{
"name": "isHeadOffice",
"lifetime": "TimeVariant",
"type": "Boolean",
"required": true,
"description": "Whether or not the location is a head office"
},
{
"name": "seatingCapacity",
"lifetime": "TimeVariant",
"type": "Decimal",
"required": false,
"description": "The seating capacity of the location"
},
{
"name": "address",
"lifetime": "Perpetual",
"type": "String",
"required": false,
"description": "The address of the location"
},
{
"name": "Amenities",
"lifetime": "TimeVariant",
"type": "String",
"collectionType": "Array",
"required": false,
"description": "A list of facilities for staff"
}
]
}
Understanding identifiers
A custom entity must have at least one identifier. Consider the following JSON fragment defining One Carter Lane, a corporate location that is both FINBOURNE Technology's London office and European headquarters, and so has an identifier for each context:
"displayName": "One Carter Lane",
"description": "FINBOURNE office and headquarters",
"identifiers": [
{
"identifierScope": "Location",
"identifierType": "OfficeId",
"identifierValue": "London"
},
{
"identifierScope": "Location",
"identifierType": "HeadquartersId",
"identifierValue": "Europe"
}
],
...
An identifier consists of three components: an identifierScope
, identifierType
and identifierValue
. The values you assign to these components combine to uniquely identify One Carter Lane in each of the contexts in which it operates. In this example:
To uniquely identify One Carter Lane as a ... | identifierScope | identifierType | IdentifierValue |
Office location |
|
|
|
Headquarters |
|
|
|
For each identifier you intend to give a custom entity, you must first call the CreatePropertyDefinition API to create a property type with the following mandatory characteristics:
A
domain
ofCustomEntity
.A
scope
with theidentifierScope
value, for exampleLocation
.A
code
with theidentifierType
value, for exampleOfficeId
orHeadquartersId
.A
constraintStyle
ofIdentifier
.A
lifeTime
ofPerpetual
.
Note you can call the SearchProperties API with a suitable filter to find all the property types that have been created as identifiers for custom entities, in case a suitable definition already exists:
curl -X GET "https://<your-domain>.lusid.com/api/api/search/propertydefinitions?filter=constraintStyle%20eq%20%27Identifier%27%20and%20domain%20eq%20%27CustomEntity%27"
-H "Authorization: Bearer <your-API-access-token>"
Creating a property type to constitute an 'Office location' identifier
The following call creates a property type with a 3-stage property key of CustomEntity/Location/OfficeId
:
curl -X POST "https://<your-domain>.lusid.com/api/api/propertydefinitions"
-H "Authorization: Bearer <your-API-access-token>"
-H "Content-Type: application/json-patch+json"
-d '{
"domain": "CustomEntity",
"scope": "Location",
"code": "OfficeId",
"displayName": "Office ID",
"dataTypeId": {"scope": "system", "code": "string"},
"lifeTime": "Perpetual",
"constraintStyle": "Identifier",
"description": "Identifier property used to identify custom entities that are office locations"
}'
Creating a property type to constitute a 'Headquarters' identifier
The following call creates a property type with a 3-stage property key of CustomEntity/Location/HeadquartersId
:
curl -X POST "https://<your-domain>.lusid.com/api/api/propertydefinitions"
-H "Authorization: Bearer <your-API-access-token>"
-H "Content-Type: application/json-patch+json"
-d '{
"domain": "CustomEntity",
"scope": "Location",
"code": "HeadquartersId",
"displayName": "Headquarters ID",
"dataTypeId": {"scope": "system", "code": "string"},
"lifeTime": "Perpetual",
"constraintStyle": "Identifier",
"description": "Identifier property used to identify custom entities that are headquarters"
}'
Creating a custom entity with unique identifier values
You can now call the UpsertCustomEntity API to create a custom entity belonging to a custom entity type. Note the following:
The underlying custom entity type is identified by its type name in the URL of the
UpsertCustomEntity
API, in this case/api/customentities/~Office
(note the~
prefix).The
identifierScope
of each identifier must be thescope
of its underlying property type, in this caseLocation
.The
identifierType
of each identifier must be thecode
of its underlying property type, in this caseOfficeId
orHeadquartersId
.The
identifierValue
of each identifier must be unique among all identifiers with the sameidentifierScope
andidentifierCode
, in this caseLondon
orEurope
.
Note: You can call the UpsertCustomEntities API to batch upsert multiple custom entities, and decide whether to fail the entire operation if one fails validation.
For example, to create a custom entity representing One Carter Lane:
curl -X POST "https://<your-domain>.lusid.com/api/api/customentities/~Office"
-H "Authorization: Bearer <your-API-access-token>"
-H "Content-Type: application/json-patch+json"
-d '{
"displayName": "One Carter Lane",
"description": "FINBOURNE office and regional headquarters",
"identifiers": [
{
"identifierScope": "Location",
"identifierType": "OfficeId",
"identifierValue": "London"
},
{
"identifierScope": "Location",
"identifierType": "HeadquartersId",
"identifierValue": "Europe"
}
],
"fields": [
{
"name": "address",
"value": "One Carter Lane, London, EC4V 5ER"
},
{
"name": "seatingCapacity",
"value": 150,
"effectiveFrom": "2021-08-01T00:00:00.00Z"
},
{
"name": "isHeadOffice",
"value": true,
"effectiveFrom": "2021-08-01T00:00:00.00Z"
},
{
"name": "Amenities",
"value": ["Bike parking", "Kitchenette", "Table tennis"],
"effectiveFrom": "2021-08-01T00:00:00.00Z"
}
]
}'
Note the use of the effectiveFrom
field to give time-variant user-specified data fields a 'valid from' date.
Providing the request is successful, the response confirms the date ranges that custom entity fields are valid between:
{
"href": "https://<your-domain>.lusid.com/api/api/customentities/~Office",
"entityType": "~Office",
"version": {
"effectiveFrom": "0001-01-01T00:00:00.0000000+00:00",
"asAtDate": "2022-09-20T14:47:45.0500820+00:00"
},
"displayName": "One Carter Lane",
"description": "FINBOURNE office and regional headquarters",
"identifiers": [
{
"identifierScope": "Location",
"identifierType": "OfficeId",
"identifierValue": "London",
"effectiveFrom": "0001-01-01T00:00:00.0000000+00:00",
"effectiveUntil": "9999-12-31T23:59:59.9999999+00:00"
},
{
"identifierScope": "Location",
"identifierType": "HeadquartersId",
"identifierValue": "Europe",
"effectiveFrom": "0001-01-01T00:00:00.0000000+00:00",
"effectiveUntil": "9999-12-31T23:59:59.9999999+00:00"
}
],
"fields": [
{
"name": "address",
"value": "One Carter Lane, London, EC4V 5ER",
"effectiveFrom": "0001-01-01T00:00:00.0000000+00:00",
"effectiveUntil": "9999-12-31T23:59:59.9999999+00:00"
},
{
"name": "Amenities",
"value": [
"Bike parking",
"Kitchenette",
"Table tennis"
],
"effectiveFrom": "2021-08-01T00:00:00.0000000+00:00",
"effectiveUntil": "9999-12-31T23:59:59.9999999+00:00"
},
{
"name": "isHeadOffice",
"value": true,
"effectiveFrom": "2021-08-01T00:00:00.0000000+00:00",
"effectiveUntil": "9999-12-31T23:59:59.9999999+00:00"
},
{
"name": "seatingCapacity",
"value": 150.0,
"effectiveFrom": "2021-08-01T00:00:00.0000000+00:00",
"effectiveUntil": "9999-12-31T23:59:59.9999999+00:00"
}
],
...
}
Retrieving custom entities and their relationships
You can retrieve a particular custom entity using the GetCustomEntity API.
You can retrieve all the custom entities of a particular type using the ListCustomEntities API, or perform a filter operation to only retrieve a matching set. Custom entities support filtering on user-specified data field values using the fields
keyword, for example:
fields[streetAddress] startswith 'One'
Note if you create relationships between a custom entity and other entities you can retrieve them using the GetCustomEntityRelationships API.
Modifying an existing custom entity type
You can optionally modify a custom entity type using the UpdateCustomEntityType API. Please note, however, this is a sensitive operation if custom entity instances of this type already exist:
If you modify the data
type
of a field (for example, fromString
toDateTime
), values of custom entities that do not match the new type cannot be retrieved. If you revert the change, original values are returned even if they have been updated in the meantime (so your updates are lost).If you modify the
lifeTime
of a field (for example, fromTimeVariant
toPerpetual
), values cannot be retrieved. If you revert the change, original values are returned even if they have been updated in the meantime.If you modify the
required
status (for example, fromTrue
toFalse
), values can be retrieved, but note that the new validation applies to all future updates to values.
A note on unsupported features
Custom entities are in the early phase of their development and do not yet support:
Properties.
Deleting a custom entity type.
Identifier properties that are specific to the custom entity type (identifier properties in the
CustomEntity
domain can currently be applied to custom entity instances of any type).Filtering on identifier properties when retrieving custom entities.