Documents

An investor is expected to provide, and be provided with, certain Documents. These Documents can range from onboarding files like copies of travel documents, to statements and annual reports generated by the giroprovider. Documents are files stored in the GiroPro database. These files can be retrieved, uploaded and replaced/updated.



DocumentAggregatedItems

When dealing with the Document endpoints in GiroPro we are mostly working with DocumentAggregatedItems. The DocumentAggregatedItem embodies the properties relevant to the actual Document files in the database. This includes properties like certain metadata properties, categorization and relevant labelId, investorIds and refererenceIds.

A DocumentAggregatedItem looks like this:

{
  "return_type": "DocumentAggregatedItem",
  "id": "9939d394-b784-4c81-b511-7ddcad9823da", // id of the document
  "labelId": "357a12a1-a12a-12ab-a123-a12a1a1ab357", // id of the connected label
  "name": "scan_passport.png", // name of the document, doesn't have to match actual file name
  "content": "", // base64 string representing the file data, empty unless uploading to save bandwidth
  "privacyLevel": "High", // level of privacy applicable to file, options are: Low, MediumLow, Medium, MediumHigh, High
  "accessLevel": "InvestorAccount", // access level required to retrieve file, options are: Investor, InvestorAccount, FundManager
  "category": "Identification", // category this file belongs to, options are: Identification, Contract, BankingStatement, Report, CAMT053, SEPA, Other, NavCalculation
  "subCategory": "None", // sub-category this file belongs to, options are: None, HistoricalOverviewOfPositionsByDate, CashMovement, DepositsOverview, DetailedTransactionsOverview, TransactionsOverview, UnitsAndValueByDate, AccountStatement, AggregatedOrder, MigrationReport, SepaExport, DirectDebitSepa, BalanceSheet, Generated, AccountMutations, Camt053File, FiscalReport, Datalake
  "extension": "png", // extension of file
  "creationDate": "2025-02-05T15:46:24.312065Z", // upload date
  "retentionDate": "2030-02-05T15:46:24.312065Z", // optional, date after which files is to be removed
  "createdByUserId": "123a12a1-a12a-12ab-a123-a12a1a1ab123", // id of user that uploaded the file
  "referencedBy": [ // list of DocumentRefferenceItems
    {
      "id": "8e053d93-afc6-4649-5a3f-08dd45fc3ecc", // id of the DocumentReferenceItem
      "documentId": "9939d394-b784-4c81-b511-7ddcad9823da", // id of the document
      "labelId": "cd3a71f1-b9ac-4e44-a06b-f89f35beafa7", // id of the label
      "referenceId": "21f35e86-f600-459a-9050-c0f4565109e3", // id of the item that is connected to this document
      "sourceType": "Investor" // type of connected item, options are: Order, Asset, SequenceFile, GiroProvider, Label, Client, BankAccount, Product, Contract, User, Investor, LedgerJournalEntryLine, DirectDebitContract, AssetEvent, CAMT053Transaction, SepaTransaction, PeriodicalSellContract, WithdrawDeposit, MarketPaymentAllocation, FundNavCalculation, JournalEntry, FundSwitch
    }
  ]
}


Uploading and Updating Documents

An investor is required to provide certain Documents, often during onboarding, like personally identifying Documents like copies of travel documents and signed agreements. GiroPro therefore offers an endpoint for uploading new Documents and an endpoint for updating existing ones.

Both of these endpoints require a body that contains a DocumentAggregatedItem. In the case of the upload endpoint it needs to contains the file data. The file data is stored in the content property and should contains the Base64-encoded payload of a dataURI.

  • So instead of a normal dataURI that looks like this:
  • data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD...

  • We want to discard the scheme, MIME type and metadata (so everything until and including the comma), so we are left with the Base64-encoded payload, in this case:
  • /9j/4AAQSkZJRgABAQAAAQABAAD...


POST /DocumentAdd

For uploading new Document files we use the /DocumentAdd endpoint. We provide a DocumentAggregatedItem that contains the relevant information, like this:

{
  "return_type": "DocumentAggregatedItem",
  "labelId": "357a12a1-a12a-12ab-a123-a12a1a1ab357",
  "name": "name_for_document",
  "privacyLevel": "Low",
  "accessLevel": "InvestorAccount",
  "category": "Other",
  "extension": "jpeg",
  "createdByUserId": "123a12a1-a12a-12ab-a123-a12a1a1ab123",
  "referencedBy": [
    {
      "labelId": "357a12a1-a12a-12ab-a123-a12a1a1ab357",
      "referenceId": "123a12a1-a12a-12ab-a123-a12a1a1ab123",
      "sourceType": "Investor"
    }
  ],
  "content": "/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAYEBQYFBAYGBQYHBwYIChAKCgkJ..."
}

We use this as the body of the request:

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/SubjectAggregator/DocumentAdd';
const options = {
  method: 'POST',
  headers: {
    accept: 'application/json', 
    'content-type': 'application/json',
    authorization: 'bearer <token>'
  },
  body: <DocumentAggregatedItem>
};

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

This call returns a PengineResult containing the created DocumentAggregatedItem.

PUT /DocumentUpdate

For updating Document files we use the /DocumentUpdate endpoint. When updating documents we only change properties of the DocumentAggregatedItem connected to the file. The endpoint doesn’t allow for the file data to be changed after uploading

It’s advised to retrieve the pre-existing DocumentAggregatedItem and apply the changes we want to make to that item:

{
            "return_type": "DocumentAggregatedItem",
            "id": "902c0b0a-1a64-403a-9f4e-ccd8c2670759",
            "labelId": "cd3a71f1-b9ac-4e44-a06b-f89f35beafa7",
            "content": "",
            "privacyLevel": "High", // Changed the privacyLevel from Low to High
            "accessLevel": "Investor",
            "category": "Identification",
            "subCategory": "None",
            "creationDate": "2025-04-17T14:32:29.690069Z",
            "createdByUserId": "00000000-0000-0000-0000-000000000000",
            "referencedBy": [
                {
                    "id": "e4c1ed96-a110-4d20-590a-08dd7dabc9e0",
                    "documentId": "902c0b0a-1a64-403a-9f4e-ccd8c2670759",
                    "labelId": "cd3a71f1-b9ac-4e44-a06b-f89f35beafa7",
                    "referenceId": "21f35e86-f600-459a-9050-c0f4565109e3",
                    "sourceType": "Investor"
                }
            ]
        }

We can then use this updated DocumentAggregatedItem as the body for the request:

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/SubjectAggregator/DocumentUpdate';
const options = {
  method: 'POST',
  headers: {
    accept: 'application/json', 
    'content-type': 'application/json',
    authorization: 'bearer <token>'
  },
  body: <DocumentAggregatedItem>
};

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

This call returns a PengineResult containing the updated DocumentAggregatedItem.

Retrieving and downloading Documents

An investor need to be able to see what Documents are available for download and download the ones they need from the investor platform. These documents can be statements and reports they need for their tax-administration as well as contracts or Document' they have uploaded themselves.

GiroPro offers two endpoints for retrieving DocumentAggregatedItems and one for downloading the actual files.

GET /GetDocumentsByInvestorAccountId/

To retrieve all the DocumentAggregatedItems associated with an InvestorAccount we use the /GetDocumentsByInvestorAccountId/ endpoint. This endpoint takes the labelId and investorAccountId as path parameters and has two optional query parameters:

Property Description Default value
skip items to skip 0
limit maximum amount of returned items (between 5 and 50) 5
const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/SubjectAggregator/GetDocumentsByInvestorAccountId/<labelId>/<investorAccountId>?skip=0&limit=5';
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 call returns a PengineResult containing a list of DocumentAggregatedItems. These items do not contain the actual file to save bandwidth.

GET /Documents/

The /Documents/ endpoint allows for more freely querying of all documents connected to a label. We can utilize this to query for documents according to certain criteria, as long as the roles and rights connected to the user allow this. This way we can query for all the documents uploaded by a User rather than just those connected to an InvestorAccount. We create these queries by adding their fields to the query parameters of the request:

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/SubjectAggregator/Documents/<labelId>?skip=0&limit=5&query[0].column=CreatedByUserId&query[0].comparisonType=exact&query[0].value=<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 creates a query entry (at index 0) and sets the column to query (CreatedByUserId) the comparison type (exact because it has to match exactly) and the value (the id of the current authorized User).

This endpoint then returns a PengineResult containing a list of DocumentAggregatedItems filtered by the provided query, in this case all Documents uploaded by the User.

We can further specify this query by including other columns like category, subCategory, privacyLevel or creationDate.

GET /DocumentGetById/

The /DocumentGetById/ endpoint returns the content of a Document based on the provided labelId and documentId.

const url = 'https://api.dev.giropro.pengine.com/PENGINE/Aggregator/SubjectAggregator/DocumentGetById/<labelId>/<documentId>';
const options = {
  method: 'GET', 
  headers: {
    accept: 'image/*',
    authorization: 'bearer <token>'
  }
};

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

This endpoint returns the binary data or byte stream of a Document depending on the file type.

When to use

To display to the investor what Documents exist we use the /GetDocumentsByInvestorAccountId/ or /Documents/ endpoints.

To download the associated file for each Document we use the /DocumentGetById/ endpoint.


Back to top

© 2026 Pengine.