Users, Investors & InvestorAccounts

Possible connections between users, investors and investor accounts

GiroPro makes a distinction between a User, an Investor and an InvestorAccount. This distinction is explained throughout the documentation on this page.

Users

Within the context of GiroPro a User is anyone with access to GiroPro. A User is always a natural person (a human). The User is the first layer between a natural person and an investment platform. It’s used for authentication and authorization and encompasses (personal) information about the natural person.

Retrieving the user

There are multiple endpoints related to retrieving User information from GiroPro.

GET /UserDetails

The first one is the /UserDetails endpoint which returns the current authenticated User:

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/SubjectAggregator/UserDetails';
const options = {
  method: 'GET', 
  headers: {
    accept: 'application/json', 
    authorization: 'bearer <token>'
  }
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));

This endpoint returns a PengineResult containing a UserDetailsDto, which looks like this:

{
    "success": true,
    "httpStatusCode": 200,
    "errors": [],
    "totalCount": 0,
    "items": [
        {
            "return_type": "UserDetailsDto",
            "userId": "123a12a1-a12a-12ab-a123-a12a1a1ab123",
            "levelOfAccess": "InvestorAccount", // type of user
            "roles": [
                // list of user-roles relevant for authorization
            ],
            "labelIds": [
                // list of labelIds
            ],
            "investorIds": [
              	// list of investorsIds connected to this user
            ],
            "investorAccountIds": [
                // list of investorAccountIds connected to the investors connected to this user
            ],
            "claims": [
                // claims related to the user and session
            ]
        }
    ],
}

GET /UserGetById/

The second way to retrieve the User is to use /UserGetById/ endpoint which returns more information about the User but requires you to provide a userId:

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/SubjectAggregator/UserGetById/<userId>';
const options = {
  method: 'GET', 
  headers: {
    accept: 'application/json', 
    authorization: 'bearer <token>'
  }
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));

This endpoint returns a PengineResult containing a UserAggregatorItem which contains multiple AggregatedCommunicationItems, which looks like this:

{
    "success": true,
    "httpStatusCode": 200,
    "errors": [],
    "totalCount": 0,
    "items": [
        {
            "return_type": "UserAggregatorItem",
            "id": "123a12a1-a12a-12ab-a123-a12a1a1ab123",
            "title": "Mr",
            "firstName": "Klaas",
            "middleName": "Nico",
            "lastName": "Spanjaard",
            "suffix": "",
            "titleSuffix": "",
            "birthDate": "1970-01-01",
            "userName": "KlaasSpanjaard",
            "language": "nl",
            "organizationId": "357a12a1-a12a-12ab-a123-a12a1a1ab357",
            "organizationType": "Label",
            "politicallyExposedPerson": false,
            "riskClassification": "Low",
            "communicationItems": [],
            "investorRoles": [
               // list of investor roles
            ],
            "aggregatedCommunicationItems": {
                "referenceId": "123a12a1-a12a-12ab-a123-a12a1a1ab123",
                "referenceType": "User",
                "addressItems": [
                    // list of known physical addresses
                ],
                "phoneItems": [
                    // list of known phone numbers
                ],
                "emailItems": [
                    // list of known email addresses
                ]
            }
        }
    ],
}

When to use

Both endpoints serve different purposes.

/UserDetails returns data relevant to the functionality of your investment platform:

  • Roles for showing and hiding of particular UI features
  • labelIds, investorIds, and investorAccountIds for retrieving portfolio, performance, and asset information

/UserGetById/ returns data relevant to display values and communication:

  • Personal information for addressing the user; “Welcome back Mr Spanjaard!”
  • Communication information for user-settings and preferences

Creating users

Creating new Users is done as part of, in accordance with, the giroprovider’s onboarding process. The username of the User in GiroPro must match the username of the User in the giroprovider’s identity provider.

Making changes to the user

To make changes to the User, like changing their name, title, risk classification or PEP-status we can use the /UserUpdate endpoint. This endpoint expects a UserAggregatorItem in the request body. Such a body looks like this:

{
    "return_type": "UserAggregatorItem", // required | immutable
    "id": "123a12a1-a12a-12ab-a123-a12a1a1ab123", // required | immutable
    "userName": "KlaasSpanjaard", // required | immutable
    "organizationId": "4563a12a1-a12a-12ab-a123-a12a1a1ab456", // required | immutable
    "organizationType": "Label", // required | immutable
    "firstName": "Klaas", // required
    "lastName": "Spanjaard", // required
    "language": "en", // required
    "aggregatedCommunicationItems": {<AggregatedCommunicatoinItems>}, // required
    "deletedCommunicationItemIds": [], 
    "title": "Mr",
    "middleName": "Nico",
    "suffix": "",
    "titleSuffix": "",
}

Some best practices in regards to updating user properties with this request body:

  • Setting an immutable property to a different value will result in a 409 status code and will not apply any of the changes made in the UserAggregatorItem.
  • Setting a value for any of the mutable properties overwrites that value in GiroPro. Therefore it’s important to set the original value for properties that are both required and mutable if you don’t intend to change them. Setting an empty string, object or array to a mutable property will overwrite it with that value. It is therefore good practice to preface a UserUpdate with a /UserGetById/ call. If you don’t intend to change a property and said property is not required it’s best to leave it out from the request body.
  • Although it is possible to add, change and remove properties like communication items through the /UserUpdate endpoint, we strongly recommended to not do this and use the respective update endpoint for each item.

PUT /UserUpdate

The /UserUpdate endpoint updates user-properties based on the provided request body and returns the updated User.

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/SubjectAggregator/UserUpdate';
const options = {
  method: 'PUT',
  headers: {
    accept: 'application/json', 
    authorization: 'bearer <token>',
    'content-type': 'application/json'
  },
  body: JSON.stringify(<UserAggregatorItem-request-body>)
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));

The endpoint returns a PengineResult containing the updated UserAggregatorItem:

{
    "success": true,
    "httpStatusCode": 200,
    "errors": [],
    "totalCount": 0,
    "items": [
        {
            "return_type": "UserAggregatorItem",
            "id": "123a12a1-a12a-12ab-a123-a12a1a1ab123",
            "title": "Mr",
            "firstName": "Klaas",
            "lastName": "Spanjaard",
            "birthDate": "1970-01-01",
            "userName": "KlaasSpanjaard",
            "language": "en",
            "organizationId": "4563a12a1-a12a-12ab-a123-a12a1a1ab456",
            "organizationType": "Label",
            "politicallyExposedPerson": false,
            "riskClassification": "Low",
            "aggregatedCommunicationItems": {
                "referenceId": "123a12a1-a12a-12ab-a123-a12a1a1ab123",
                "referenceType": "Investor",
                "addressItems": [],
                "phoneItems": [],
                "emailItems": []
            }
        }
    ],
}

When to use

It’s recommended to only us the /UserUpdate endpoint when you want to allow the User to make changes to their name or title. Other changes related to the User should be made through their respective endpoints (in the case of communication items) or should be made by the giro provider (in the case of roles or risk-assessment)



Investors

An Investor is either a natural person (a human) or a legal entity (a company) connected to one or multiple Users. In the case of a natural-person-investor there is always exactly one User connected to the investor. In the case of a legal-entity-investor there is at least one User connected to the investor.

When dealing with a legal-entity-investor there might be multiple Users with varying degrees of access to the Investor and its accounts. These can be the owner(s) of the entity, personnel with access to put in or administrate orders, or an accountant for bookkeeping purposes.

A User can be connected to multiple Investors. They can be investing as a natural person as well as through a legal entity, or they might be involved with multiple legal entities at the same time.

Retrieving the investor(s)

There are multiple endpoints related to retrieving Investor information from GiroPro. All of these endpoints return one or multiple InvestorAggregatorItems, which look like this:

{
    "return_type": "InvestorAggregatorItem",
    "id": "987a12a1-a12a-12ab-a123-a12a1a1ab987",
    "aggregatedUserRoles": [
      	{
            "userAggregatedItem": {
                // user data
            },
            "userId": "123a12a1-a12a-12ab-a123-a12a1a1ab123",
            "individualHolderRole": "Owner",
            "state": "Active",
            "taxIdentificationNumbers": [
                {
                    "taxIdentificationNumber": "123456789",
                    "taxCountry": "NLD"
                }
            ],
            "startDate": "2005-01-01",
            "onboardingData": "",
            "isReadOnly": false
        }
    ],
    "aggregatedTypeOfInvestor": {
        "individualHolder": {
            "userAggregatedItem": {
                // user data
            },
            "userId": "123a12a1-a12a-12ab-a123-a12a1a1ab123",
            "investorState": "active"
        }
    },
    "userRoles": [
        {
            "userId": "123a12a1-a12a-12ab-a123-a12a1a1ab123",
            "individualHolderRole": "Owner",
            "state": "Active",
            "taxIdentificationNumbers": [
                {
                    "taxIdentificationNumber": "123456789",
                    "taxCountry": "NLD"
                }
            ],
            "startDate": "2005-01-01",
            "onboardingData": "",
            "isReadOnly": false
        }
    ],
    "labelId": "357a12a1-a12a-12ab-a123-a12a1a1ab357",
    "typeOfInvestor": {
        "individualHolder": {
            "userId": "123a12a1-a12a-12ab-a123-a12a1a1ab123",
            "investorState": "active"
        }
    },
    "taxIdentificationNumbers": [
        {
            "taxIdentificationNumber": "123456789",
            "taxCountry": "NLD"
        }
    ],
    "onboardingData": "<JSON object containing onboarding data>",
    "riskClassification": "Low",
    "aggregatedCommunicationItems": {
        "referenceId": "00000000-0000-0000-0000-000000000000",
        "referenceType": "Investor",
        "addressItems": [],
        "phoneItems": [],
        "emailItems": []
    }
}

GET /InvestorGetById/

/InvestorGetById/ returns the singular InvestorAggregatorItem associated with the provided label- and investorId:

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/SubjectAggregator/InvestorGetById/<labelId>/<investorId>';
const options = {
  method: 'GET', 
  headers: {
    accept: 'application/json', 
    authorization: 'bearer <token>'
  }
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));

GET /InvestorsGetByUserId/

/InvestorsGetByUserId/ returns all InvestorAggregatorItems for a specific label connected to a User by providing the label- and investorId:

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/SubjectAggregator/InvestorsGetByUserId/<labelId>/<userId>';
const options = {
  method: 'GET', 
  headers: {
    accept: 'application/json', 
    authorization: 'bearer <token>'
  }
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));

GET /InvestorsGetByInvestorAccountId/

/InvestorsGetByInvestorAccountId/ returns all InvestorAggregatorItems for a specific label connected to a specific InvestorAccount by providing the label- and investorAccountId:

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/SubjectAggregator/InvestorsGetByInvestorAccountId/<labelId>/<investorAccountId>';
const options = {
  method: 'GET', 
  headers: {
    accept: 'application/json', 
    authorization: 'bearer <token>'
  }
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));

When to use

As the return type of each endpoint is the same, these are the things to consider:

  • /InvestorGetById/ can be used for displaying information about a specific Investor and to retrieve the InvestorAccounts connected to it
  • /InvestorsGetByUserId/ can be used to create a summary of Investors connected to a User for a specific label
  • /InvestorsGetByInvestorAccountId/ can be used to retrieve other Investors connected to an InvestorAccount. This is useful when dealing with specific types of InvestorAccounts, like shared accounts or child accounts, which have multiple investors connected to them.

Creating investors

Creating new Investors is done as part of, and in accordance with, the giroprovider’s onboarding process.

Making changes to the investor

Making changes to the Investor can be done through the /InvestorUpdate endpoint, but it is recommended to not do so from the Investor platform as the Investor’s constituent parts either have their own update-endpoints or can only be changed by the giroprovider.



InvestorAccounts

An InvestorAccount represents an account in which money can be deposited, and withdrawn from, for the purpose of investing. An InvestorAccount is connected to one or multiple Investors.

GET InvestorAccountsGetByInvestorId

The InvestorAccountsGetByInvestorId endpoint returns the InvestorAccounts connected to an Investor by the provided labelId and investorId:

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/SubjectAggregator/InvestorAccountsGetByInvestorId/<labelId>/<investorId>';
const options = {
  method: 'GET', 
  headers: {
    accept: 'application/json',
    authorization: 'bearer <token>'
  }
};

fetch(url, options)
  .then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.error(err));

This returns a PengineResult containing a list of InvestorAccountAggregatedItems:

{
  "success": true,
  "httpStatusCode": 200,
  "errors": [],
  "totalCount": 0,
  "items": [
    {
      "return_type": "InvestorAccountAggregatedItem",
      "investors": [
        // list of InvestorAggregatedItems
      ],
      "investorIds": [
        // list of InvestorIds
      ],
      "isBlocked": false,
      "externalId": "12345",
      "pledgee": "",
      "pledgedAmount": 0.0,
      "communications": [],
      "investorAccountState": "Active",
      "onboardingData": "",
      "dividendOption": "ReInvest",
      "autoPayoutStockDividend": false,
      "isNew": true,
      "relationIds": [],
      "relations": [],
      "riskClassification": "Low",
      "tags": [
        "One person"
      ],
      "protectedInvestorAccount": false,
      "bankAccount": {
        // associated bankAccount
      },
      "discriminator": "InvestorAccount",
      "id": "6b6cb028-6362-409d-92d6-1224fdb0084d",
      "labelId": "cd3a71f1-b9ac-4e44-a06b-f89f35beafa7",
      "counterBankAccountId": "5b0a8dad-0e5a-4f1b-9c4a-332113f962c4",
      "currency": "EUR",
      "accountNumber": "20507194",
      "contractId": "4fe992b8-d994-4024-959a-8e8421210a86",
      "creationDate": "2005-07-01T00:00:00Z",
      "validFromDate": "2005-07-01T00:00:00Z",
      "versionState": "None",
      "aggregatedCommunicationItems": {
        // list of AggregatedCommunicationItems
      }
    }
  ]
}


InvestmentAccount

Throughout the API you will see mention of, and reference to, something called an InvestmentAccount. The InvestmentAccount is the base type for both the InvestorAccount and the FundAccount, which is used for the fund administration.

The InvestmentAccount contains the Cash and Portfolio, as both of these are present in the InvestorAccount and the FundAccount.


Back to top

© 2026 Pengine.