# Stonal Public API
> Public API documentation for Stonal: asset management, document storage, user management, webhooks, and legacy endpoints.
Base URL: https://docs.stonal.io
---
# Assets hierarchy
URL: https://docs.stonal.io/md/assets/hierarchy
# Assets hierarchy
## Overall hierarchy
These are the relations between assets:

## Asset type families
We have the following asset type families:
| Type | Physical / Logical | Description |
| ------------------ | ------------------ | --------------------------------------------------------------------- |
| `BUILDING_GROUP` | ๐ข Logical | A group of buildings |
| `BUILDING` | ๐ผ Logical | A building like it's handled in your ERP |
| `BUILDING_SECTION` | ๐ผ Logical | A section of a building (can be a stairwell, an entrance, etc.) |
| `PLAN\|INDOOR` | ๐ข Physical | A floorplan of a building (usually has multiple levels) |
| `PLAN\|OUTDOOR` | ๐ข Physical | A plan of an outdoor area (usually has only one level) |
| `LEVEL` | ๐ข Physical | A floor of a building (as like `PLAN\|INDOOR`) |
| `LEVEL_SECTION` | ๐ผ Logical | A section of a level (vertical division within a floor) |
| `RENTED_UNIT` | ๐ผ Logical | Something that could be rented (usually associated with a contract) |
| `ZONE` | ๐ผ Logical | A grouping of spaces (can be an apartment, a set of offices, etc.) |
| `SPACE` | ๐ข Physical | A space in a building (can be an office, a room, etc.) |
| `OPENING` | ๐ข Physical | An opening in a space (can be a door, a window, etc.) |
| `SPACE_BOUNDARY` | ๐ข Physical | A boundary of a space (can be a wall, a floor, a ceiling, etc.) |
| `EQUIPMENT` | ๐ข Logical | A physical or logical equipment (can a light, a door, a window, etc.) |
## Specific asset types
We have more than 4000 different types of asset.
The full list of types can be explored in [this Metabase dashboard with built-in filters](https://api.stonal.io/metabase/public/dashboard/840e5a5f-56e7-437d-aa68-3a8827eeb832?code_de_l%27asset=&nom_de_l%27asset=&tab=231-plans).
The full list of equipments is [published on buildingsmart.org](https://identifier.buildingsmart.org/uri/stonal/stonal-equip/1.0).
---
# Authenticating users
URL: https://docs.stonal.io/md/users/authenticating-users
# Authenticating users
At Stonal, we use a dedicated service for identity management.
:::info
We support only the OpenID Connect (OIDC) protocol for authentication.
:::
## Authentication
### Workflow
We use **authorization_code** grant type flow for OIDC authentication.
```mermaid
sequenceDiagram
actor A as User
participant B as Platform Stonal (Web App)
participant C as API GATEWAY
participant D as SSO
participant E as Stonal Service (Stonal API)
A->>B: 1 - goes to
B->>C: 2 - Request user authorization
C->>D: 3 - Redirect to SSO
D->>C: 4 - Authorization Code
C->>D: 5 - Request Access Token
D-->>C: 5 - Return Access Token
C->>B: 6 - Redirect to Platform
B->>C: 7 - Request data via private Stonal API with Session
C->>E: 7 - Request data via private Stonal API with Access Token
E->>B: 8 - Return data
```
### Description
All Stonal APIs are protected by authentication and authorization mechanisms.
By default, our platform frontend fetches the user connected authorization through an internal API Gateway.
The API Gateway is responsible for checking if current user is connected via a secured cookie.
Depending on whether the user is connected, the API Gateway will use the access and refresh tokens (JWT tokens)
stored alongside with the cookie in a private data store to fetch the user connected authorizations and access
Stonal private APIs.
If the user is not connected, the API Gateway will redirect the user to our SSO service to authenticate.
When authenticated, our SSO service will redirect the user back to the API Gateway with an authorization code
which will be exchanged for an access token and a refresh token associated with the user and stored in the private data store
alongside with the cookie.
:::info
Be aware, users account need to be created before users can authenticate through the SSO.
Go to [Managing users](../md/users) for more information.
:::
## Identity delegation
We support identity delegation using OIDC protocol.
When we add a new identity provider, we will proceed as follows:
- **SSO**: We save a new identity provider for the customer (we call it `organization`) that would like to use their own identity management system. (eg: `orga`)
- **Customer Identity System**: Customer creates a new client (usually customer called it `stonal`) OIDC protocol.
The customer will provide us the following information:
- `discovery endpoint`: The identity provider [standard OIDC discovery endpoint](https://openid.net/specs/openid-connect-discovery-1_0.html#rfc.section.4),
- `client_id`: The client ID associated with client created on the customer identity system for our SSO,
- `client_secret`: The client secret of the client created on the customer identity system for our SSO.
:::info
We only need one *required* information to be present in identity provider token response: **email** claims in payload.
:::
We provide the following information to the customer:
- `redirect_uri`: The redirect URI of the client created on the customer identity system that will redirect to our SSO.
At the end, we will provide the final link to our customer to be used if they want their user to be authenticated though their own SSO.
:::info
Our customers includes this link into their own portal.
:::
:::warning
This step requires usually a small meeting between our technical team and the customer technical team.
:::
---
# Creating a user
URL: https://docs.stonal.io/md/users/legacy/create-a-user
# Creating a user
This guide walks through the process of creating a new user in the Stonal platform using the API. The process involves several steps to gather required information before creating the user.
:::info Prerequisites
- A valid set of API credentials
- A valid access token (see: [**Get a token**](/api/get-access-token))
- An organization code
:::
### Step 1: Search for an existing user
Before creating a new user, check if they already exist to avoid duplicates.
See: [**API Specification**](/api/legacy/search-user-on-organization)
```http
POST /v1/organizations/{organizationCode}/users/search
```
Request body:
```json
{
"email": "firstName.lastName@corp.com"
}
```
If the user exists, you'll receive a 200 response with user details (See: [**Modifying a user**](/md/users/legacy/modify-a-user)). If not, proceed with user creation.
### Step 2: Lookup the necessary user information
Get the list of available companies that can be assigned to the user:
See: [**API Specification**](/api/legacy/find-all-companies-by)
```http
GET /v1/organizations/{organizationCode}/users/companies
```
Query parameters:
- `page` (optional): Page number (default: 0)
- `size` (optional): Items per page (default: 100)
The resulting companies can be used to create a user with the `company` field.
Thus giving the user the right to access the assets of the corresponding company.
Get available user profiles:
See: [**API Specification**](/api/legacy/find-all-profiles-by)
```http
GET /v1/organizations/{organizationCode}/users/profiles
```
The resulting profiles can be used to create a user with the `profile` field.
Get available user groups:
See: [**API Specification**](/api/legacy/find-all-groups-by)
```http
GET /v1/organizations/:organizationCode/users/groups
```
The resulting groups can be used to create a user with the `groupUids` field.
Retrieve available authorization models:
See: [**API Specification**](/api/legacy/find-all-authorizations-by)
```http
GET /v1/organizations/{organizationCode}/users/authorizations
```
The response includes:
- Model ID
- Name
- Description
- Available applications
The resulting authorization models can be used to create a user with the `authorization.modelId` field.
Thus giving the user the right to access the corresponding applications.
Fetch available permissions:
See: [**API Specification**](/api/legacy/find-all-permissions-by)
```http
GET /v1/organizations/{organizationCode}/users/permissions?pageNumber=1&pageSize=10
```
Query parameters:
- `pageNumber` (required): Page number
- `pageSize` (required): Items per page
- `type` (optional): Permission type (e.g., ASSET)
- `subType` (optional): Permission sub-type (e.g., PORTFOLIO)
- `q` (optional): Search query on label
- `sortField` (optional): Field to sort by
- `sortOrder` (optional): Sort order (asc/desc)
The resulting permissions can be used to create a user with the `permissionUids` field.
Thus giving the user the right to access the corresponding assets.
### Step 3: Create the user
Finally, create the user with all gathered information:
See: [**API Specification**](/api/legacy/create)
```http
POST /v1/organizations/{organizationCode}/users
```
Request body:
```json
{
"email": "firstName.lastName@corp.com",
"firstName": "firstName",
"lastName": "lastName",
"company": "companyName",
"fromExternalIdp": false,
"groupUids": ["0192d7b7-2994-7ad5-9952-26862f33c21a"],
"authorization": {
"modelId": "2b2e8a4b-bfbd-4c56-b8d6-c8cb1d8c58ba",
"profile": "Developer",
"asset": {
"all": false
}
},
"permissionUids": [
"0192d7b7-2994-7ad5-9952-26862f33c21a",
"0192d7b7-7073-7e58-896c-07113f22363a"
]
}
```
Required fields:
- `email`: User's email address
- `firstName`: User's first name
- `lastName`: User's last name
- `company`: Company name (must be from available companies)
- `groupUids`: Array of group UIDs
- `authorization`: Authorization configuration
- `modelId`: ID of the authorization model
- `profile`: User profile
- `asset`: Asset access configuration
- `permissionUids`: Array of permission UIDs
Possible responses:
- 201: User created successfully
- 202: User creation appears successful but cannot be confirmed
- 409: User already exists
### Notes
- All email addresses must be unique in the system
- The company assignment affects which assets the user can access
- If `fromExternalIdp` is true, the user must authenticate through their identity provider
- Asset access can be granted to all assets (`"all": true`) or specific assets using `permissionUids`
- Permission UIDs must be valid and retrieved from the permissions endpoint
### Error Handling
Common error scenarios:
- Invalid authorization model ID
- Invalid company name
- Duplicate email address
- Invalid permission UIDs
- Missing required fields
Always check the response status and handle errors appropriately in your implementation.
---
# Getting Started
URL: https://docs.stonal.io
# Getting Started
## Get a Stonal account
You can get a Stonal account by contacting the API support (by email or slack).
## Get some authentication credentials
You need to define an API user and [generate a personal access token (PAT)](/md/auth/pat) for it.
## Define what you want to do
- [Manage assets](/md/assets)
- [Manage documents](/md/documents)
- [Manage users](/md/users)
- [Receive document and asset changes](/md/webhooks)
- [Manage stakeholders](/md/stakeholders)
- [Manage contracts](/md/contracts)
## ๐ง Using AI coding agents
This documentation follows the [llms.txt](https://llmstxt.org/) convention so AI coding agents can work autonomously on your Stonal integration. Point your agent (Claude Code, Cursor, GitHub Copilot, etc.) at one of these URLs:
- [`https://docs.stonal.io/llms.txt`](https://docs.stonal.io/llms.txt) โ concise index with links to every guide and API reference
- [`https://docs.stonal.io/llms-full.txt`](https://docs.stonal.io/llms-full.txt) โ full documentation bundled into a single file
Both are refreshed on every build, so your agent always sees the current API.
---
# Ingesting data
URL: https://docs.stonal.io/md/assets/ingest
# Ingesting data
This guide explains how to ingest and manage assets like buildings, spaces, and equipment using the Stonal ingestion API.
:::info
You will need to get a personal access token to use the ingestion APIs.
:::
All ingestion requests use [the ingestion API](/api/assets/upsert-assets) with the following structure:
```bash title="๐ ingest.sh"
#!/bin/sh -ex
# Your organization
STONAL_ORG=DEMO
# Fetch your token from here: https://app.stonal.io/users/app
STONAL_TOKEN=9631e269-e21e-4ce6-92a9-c6f7ed1fa98a
curl \
https://api.stonal.io/datalake/v1/organizations/$STONAL_ORG/assets \
-v \
-X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $STONAL_TOKEN" \
-d @payload.json
```
## Root level assets
`BUILDING_GROUP`, `BUILDING`
These are the top-level assets in your hierarchy. A `BUILDING_GROUP` represents a site containing multiple buildings, and `BUILDING` represents a logical building as managed in your ERP.
```json title="๐ root-assets.json"
{
"assets": [
{
"externalIds": {
"CODE": "SITE_001"
},
"type": "BUILDING_GROUP",
"name": "Residence du Parc",
"properties": {
"simple": {
"ADDRESS": "123 Rue de la Paix, Paris, France"
}
}
},
{
"externalIds": {
"CODE": "SITE_001_BAT_001"
},
"type": "BUILDING",
"name": "Bรขtiment A",
"parent": {
"externalIds": {
"CODE": "SITE_001"
}
}
}
]
}
```
## Building and Level Sections
`BUILDING_SECTION`, `LEVEL_SECTION`
Building and level sections provide logical divisions within your building structure. A `BUILDING_SECTION` represents a section of a building (such as a stairwell, entrance, or wing), while a `LEVEL_SECTION` represents a vertical division within a floor.
```json title="๐ building-level-sections.json"
{
"assets": [
{
"externalIds": {
"CODE": "SITE_001_ENTRANCE_A"
},
"type": "BUILDING_SECTION",
"name": "Entrรฉe A",
"parent": {
"externalIds": {
"CODE": "SITE_001_BAT_001"
}
}
},
{
"externalIds": {
"CODE": "SITE_001_LEVEL_RDC_SECTION_A"
},
"type": "LEVEL_SECTION",
"name": "Section A - RDC",
"parent": {
"externalIds": {
"CODE": "SITE_001_LEVEL_RDC"
}
},
"groupedBy": [
{
"externalIds": {
"CODE": "SITE_001_ENTRANCE_A"
}
}
]
}
]
}
```
:::tip
The `groupedBy` field on a `LEVEL_SECTION` can link it to a `BUILDING_SECTION`, allowing you to associate floor sections with their corresponding building sections (e.g., stairwells or entrances that span multiple floors).
:::
## Physical building structure
`PLAN|INDOOR`, `LEVEL`, `SPACE`
Physical assets describe the actual geometry of your buildings. A `PLAN|INDOOR` groups multiple floors, a `LEVEL` represents a single floor, and `SPACE` represents rooms.
```json title="๐ physical-assets.json"
{
"assets": [
{
"externalIds": {
"CODE": "SITE_001_INDOOR_001"
},
"type": "PLAN|INDOOR",
"name": "Plan Bรขtiment A",
"parent": {
"externalIds": {
"CODE": "SITE_001"
}
},
"groupedBy": [
{
"externalIds": {
"CODE": "SITE_001_BAT_001"
}
}
]
},
{
"externalIds": {
"CODE": "SITE_001_LEVEL_RDC"
},
"type": "LEVEL",
"name": "Rez-de-chaussรฉe",
"parent": {
"externalIds": {
"CODE": "SITE_001_INDOOR_001"
}
}
},
{
"externalIds": {
"CODE": "SITE_001_SPACE_001"
},
"type": "SPACE",
"name": "Bureau 101",
"parent": {
"externalIds": {
"CODE": "SITE_001_LEVEL_RDC"
}
},
"groupedBy": [
{
"externalIds": {
"CODE": "SITE_001_ZONE_001"
}
}
]
}
]
}
```
:::tip
The `groupedBy` field on a `PLAN|INDOOR` links it to a logical `BUILDING`. This allows you to associate physical plans with your ERP's building structure.
:::
## Logical assets
`RENTED_UNIT`, `ZONE`
Logical assets organize spaces for business purposes. A `RENTED_UNIT` represents a housing unit that can be rented (linked to contracts), and a `ZONE` groups multiple spaces together (an apartment, a set of offices, etc.).
```json title="๐ logical-assets.json"
{
"assets": [
{
"externalIds": {
"CODE": "SITE_001_APT_101"
},
"type": "RENTED_UNIT",
"name": "Appartement 101",
"parent": {
"externalIds": {
"CODE": "SITE_001_BAT_001"
}
}
},
{
"externalIds": {
"CODE": "SITE_001_ZONE_001"
},
"type": "ZONE",
"name": "Appartement T3",
"parent": {
"externalIds": {
"CODE": "SITE_001_LEVEL_RDC"
}
},
"groupedBy": [
{
"externalIds": {
"CODE": "SITE_001_APT_101"
}
}
]
}
]
}
```
:::tip
The `groupedBy` field on a `ZONE` links it to a `RENTED_UNIT`, allowing you to associate physical spaces with rentable units and their contracts.
:::
## Equipments and Openings
`EQUIPMENT`, `OPENING`
Openings represent doors, windows, and other passages within a space. Equipments are items installed in openings (doors, windows) or directly in spaces (radiators, etc.).
```json title="๐ equipments-openings.json"
{
"assets": [
{
"externalIds": {
"CODE": "SITE_001_OPENING_001"
},
"type": "OPENING",
"name": "Porte entrรฉe Bureau 101",
"parent": {
"externalIds": {
"CODE": "SITE_001_SPACE_001"
}
}
},
{
"externalIds": {
"CODE": "SITE_001_DOOR_001"
},
"type": "EQUIPMENT",
"name": "Porte bois",
"parent": {
"externalIds": {
"CODE": "SITE_001_OPENING_001"
}
}
}
]
}
```
## Response format
A successful ingestion returns the created/updated assets with their generated `uid`:
```json title="โก๏ธ output"
{
"assets": [
{
"uid": "272e9b9c-31f1-4a6b-86af-a8737031a2bc",
"externalIds": {
"CODE": "SITE_001"
},
"type": "BUILDING_GROUP",
"name": "Residence du Parc",
"properties": {
"source": "DEFAULT",
"simple": {
"ADDRESS": "123 Rue de la Paix, Paris, France"
}
}
}
]
}
```
## Uses cases
### Updating an asset
:::info
We can refer to an asset using: `uid` or **one** `externalIds`. Same when referring to relations: `parent`, `groups`, `groupedBy`or `groupingAssetsToAdd`
:::
#### Updating asset's parent
Here weโre simply changing the parent of an asset
```json title="๐ updating-asset-parent.json"
{
"assets": [{
"uid": "1712974c-3758-4e67-8717-1aa2441efc29",
"parent": { "uid": "d3e98110-e494-4cac-a508-38058681fa62" }
}]
}
```
For removing a parent, we need to explicitly set it to `null`
```json title="๐ removing-asset-parent.json"
{
"assets": [{
"uid": "1712974c-3758-4e67-8717-1aa2441efc29",
"parent": null
}]
}
```
#### Adding or changing asset's externalId
:::info
When we want to change an external identifier, we need to specify the asset's `uid`.
:::
```json title="๐ adding-or-updating-an-external_id.json"
{
"assets": [{
"uid": "1712974c-3758-4e67-8717-1aa2441efc29",
"externalIds": {
"CODE": "SITE_001",
"PIH": "00001"
}
}]
}
```
#### Updating asset's expiration date
```json title="๐ adding-or-updating-an-expiration_date.json"
{
"assets": [{
"uid": "1712974c-3758-4e67-8717-1aa2441efc29",
"expirationDate": "2040-10-09"
}]
}
```
#### Updating asset's effective date
```json title="๐ adding-or-updating-an-effective_date.json"
{
"assets": [{
"uid": "1712974c-3758-4e67-8717-1aa2441efc29",
"effectiveDate": "2010-01-01"
}]
}
```
### Declaring properties for a building
Data declaration can be done while creating an asset or can be done later when updating an asset.
:::info
Automatically set `effectiveDate` to current date and `expirationDate` to max system date
:::
:::info
Keys of `simple` object are referring to propertyโs `code`.
This code can be found in [our DQC product](https://app.stonal.io/dqc) for each asset's type
In this example we are on an BUILDING, its property named *Adresse* has `ADDRESS` for code.
Screenshoot from DQC on a URL like `https://app.stonal.io/dqc/$ORGANIZATION_CODE/asset-types/EQUIPMENT/assets/$ASSET_UID`

Nb: code can also have uuid format
In this example we are on an EQUIPMENT, its property named *Date dernier renouvellement* has `43f1f705-b3b3-4ff3-9ca3-a5c5ed638eef` for code.
Screenshoot from DQC on a URL like `https://app.stonal.io/dqc/$ORGANIZATION_CODE/asset-types/EQUIPMENT/assets/$ASSET_UID`

:::
```json title="๐ adding-address-and-date-for-a-building.json"
{
"assets": [{
"externalIds": {
"CODE": "BUILDING_001"
},
"properties": {
"source": "PREMIANCE", // Optionnal - by default it's set to "DEFAULT"
"simple": {
"ADDRESS": "28, cours Albert 1er, 75008 PARIS, France",
"BUILDING_DATE": "2025-11-25"
}
}
}]
}
```
#### Ignoring unknown properties
:::tip
If we want to ignore unknown properties and create existing ones (instead of throwing and stopping ingestion) we can set `ignoreUnknowns` parameter to `true` in `options`
:::
```json title="๐ adding-data-for-existing-properties-and-ignore-unkown-properties.json"
{
"options": {
"properties": {
"ignoreUnknowns": true
}
},
"assets": [
{
"uid": "2009d42d-0f31-4c0f-a395-b7688e3b667d",
"properties": {
"source": "PREMIANCE",
"simple": {
"NON_EXISTING": "A value", // Will be ignored
"ADDRESS": "28, cours Albert 1er, 75008 PARIS, FRANCE", // Will be created because this property exists
}
}
}
]
}
```
The unknown properties will be ignored and not inserted and consequently not returned
```json title="โก๏ธ output"
{
"assets": [
{
"uid": "2009d42d-0f31-4c0f-a395-b7688e3b667d",
"externalIds": {
"CODE": "BUILDING_001"
},
"type": "BUILDING",
"name": "Bรขtiment 1",
"properties": {
"source": "PREMIANCE",
"simple": {
"ADDRESS": "28, cours Albert 1er, 75008 PARIS, FRANCE"
}
}
}
]
}
```
#### Declaring time-based properties
:::info
If not specified, automatically set `startDate` to current date and `endDate` to max system date
:::
```json title="๐ defining-tenants-by-dates-for-a-building.json"
{
"assets": [{
"externalIds": {
"CODE": "BUILDING_001"
},
"properties": {
"precise": {
"TENANT_NAME": [
{
"rawValue": "Laboratoire A",
"endDate": "2029-01-31"
},
{
"rawValue": "Avocats B",
"startDate": "2029-02-01",
"endDate": "2035-01-01"
}
]
}
}
}]
}
```
### Deleting an asset
:::warning
Only the `uid` is allowed when specifying the asset to delete. Using `externalIds` is not allowed for deletion.
:::
:::info
We cannot set a `deletedAt` value that is more than 1h after or before the current time.
:::
:::info
The asset to delete must not have any child assets. It may still have parent assets, but it cannot reference other assets as its descendants.
:::
```json title="๐ deleting-an-asset.json"
{
"assets": [{
"uid": "1712974c-3758-4e67-8717-1aa2441efc29",
"deletedAt": "2025-02-21T23:30:58.284Z"
}]
}
```
---
# Managing documents
URL: https://docs.stonal.io/md/documents
# Managing documents
This document introduces the concepts behind Stonal's document management platform enhanced by artificial intelligence.
Understanding these concepts will help you navigate the system, from uploading documents to leveraging AI-powered analysis and organization.
## Overview
The platform enables organizations to **store**, **organize**, and **analyze documents** efficiently.
It provides advanced AI capabilities to automatically **extract**, **categorize**, and **relate information** from the uploaded files.
The main objectives of the system are:
- Centralizing all documents related to assets and activities.
- Enhancing document quality and structure via automated AI processing.
- Ensuring traceability and controlled access across organizational structures.
## Core Concepts
### Organizations
Organizations are the primary structure used to group and manage documents.
Each document is attached to a specific organization.
:::info
The `organizationCode` is used throughout the APIs to associate actions with the correct organization.
:::
### Documents
Documents are any files (PDFs, images, spreadsheets, etc.) uploaded into the system.
Once uploaded, documents can be enriched with metadata, structured into folders, and linked to various assets (such as facilities or buildings).
### AI Enrichment
Once a document is uploaded, the platform's AI services can perform operations such as:
- **Automatic extraction** of key metadata (dates, addresses, asset identifiers, etc.).
- **Classification** of documents into predefined categories.
- **Contextual linking** to existing assets and documentation structures.
:::note
AI enrichment can happen immediately after upload or as part of scheduled processing.
:::
### Permissions and Access
The platform enforces strict access control based on organizational memberships and user permissions, ensuring that documents are securely managed and accessible only to authorized users.
## Uploading Documents
Uploading documents is the first step towards building a structured and intelligent document repository.
Documents can be uploaded through the public API by providing:
- The file itself
- An optional **manifest** JSON providing contextual information
Uploaded documents are processed and stored, making them available for AI enrichment and further organization.
---
# Personal Access Token (Recommended)
URL: https://docs.stonal.io/md/auth/pat
# Personal Access Token (Recommended)
Personal access tokens require minimal setup and don't need credential exchange between your organization and Stonal. When you generate a token, Stonal only stores its hashed version, enhancing security.
## Creating an API user
:::info Prerequisites
You must have admin account access.
:::
1. Navigate to the [**User Management Interface**](https://app.stonal.io/users) (staging is [here](https://app.stonal-staging.io/users))
2. Create an api user account
3. Store the password securely - you'll need it for the next step
4. You may log out after completing this step

## Generating a Personal Access Token
1. Log in using the api user credentials you created
2. Navigate to the [**Personal Access Token Management Interface**](https://app.stonal.io/users/pat) (staging is [here](https://app.stonal-staging.io/users/pat))
:::info
Select an appropriate token duration based on your security requirements.
:::

3. After generation, copy and securely store your token using the provided button

## Using Your Personal Access Token
Include your token in the `Authorization` header of all API requests using the `Bearer` prefix:
```
Authorization: Bearer
```
For example:
```
Authorization: Bearer 42e66530-251a-48ca-8da7-22768cbf6982
```
---
# Authenticating
URL: https://docs.stonal.io/md/auth
# Authenticating
We currently support two authentication methods for our APIs:
- [Personal access tokens](/md/auth/pat) (recommended) - the simplest approach requiring minimal setup
- [OpenID Connect](/md/auth/openid) - for organizations requiring more advanced authentication flows
---
# Creating a user
URL: https://docs.stonal.io/md/users/create-a-user
# Creating a user
This guide walks through the process of creating a new user in the Stonal platform using the API. The process involves several steps to gather required information before creating the user.
:::info Prerequisites
- A valid set of API credentials
- A valid access token (see: [**Get a token**](/md/auth))
- An organization code
:::
### Step 1: Search for an existing user
Before creating a new user, check if they already exist to avoid duplicates.
See: [**API Specification**](/api/users/get-users-paginated)
```http
GET /v2/organizations/{organizationCode}/users?pageNumber=1&pageSize=10&q=firstName.lastName@corp.com
```
If the user exists, you'll receive a 200 response with user details (See: [**Modifying a user**](/md/users/modify-a-user)). If not, proceed with user creation.
### Step 2: Lookup the necessary user information
#### User groups
Get available user groups:
See: [**API Specification**](/api/users/get-groups)
```http
GET /v1/organizations/:organizationCode/groups?pageNumber=1&pageSize=10
```
The resulting groups can be used to create a user with the `groupUids` field.
#### Application groups
Retrieve available application groups:
See: [**API Specification**](/api/users/get-permissions)
```http
GET /v1/organizations/:organizationCode/users/permissions?pageNumber=1&pageSize=10&type=SCOPE_GROUP&subType=APPLICATION
```
The resulting application groups can be used to create a user with the `permissions.uid` field.
Thus giving the user the right to access the corresponding applications.
#### Profiles
Get available user profiles:
See: [**API Specification**](/api/users/get-permissions)
```http
GET /v1/organizations/:organizationCode/users/permissions?pageNumber=1&pageSize=10&type=PROFILE
```
The resulting profiles can be used to create a user with the `permissions.uid` field.
#### Assets
Here is the list of currently available `ASSET` subtypes:
- `PORTFOLIO`
- `COMPANY`
- `FACILITY`
- `BUILDING_GROUP`
- `BUILDING`
##### Companies
See: [**API Specification**](/api/users/get-permissions)
```http
GET /v1/organizations/:organizationCode/users/permissions?pageNumber=1&pageSize=10&type=ASSET&subType=COMPANY
```
The resulting permissions can be used to create a user with the `permissions.uid` field.
Thus giving the user the right to access the corresponding companies.
##### Portfolios
See: [**API Specification**](/api/users/get-permissions)
```http
GET /v1/organizations/:organizationCode/users/permissions?pageNumber=1&pageSize=10&type=ASSET&subType=PORTFOLIO
```
The resulting permissions can be used to create a user with the `permissions.uid` field.
Thus giving the user the right to access the corresponding portfolios.
##### Report groups
See: [**API Specification**](/api/users/get-permissions)
```http
GET /v1/organizations/:organizationCode/users/permissions?pageNumber=1&pageSize=10&type=REPORT_GROUP
```
The resulting permissions can be used to create a user with the `permissions.uid` field.
Thus giving the user the right to access the underlying reports.
### Step 3: Create the user
Finally, create the user with all gathered information:
See: [**API Specification**](/api/users/create-user)
```http
POST /v2/organizations/{organizationCode}/users
```
Request body:
```json
{
"email": "firstName.lastName@corp.com",
"firstName": "firstName",
"lastName": "lastName",
"password": "SecureP@ssw0rd!",
"fromExternalIdp": false,
"allAssets": false,
"userGroupUids": [
"019619df-4768-76b7-81e3-2c56d374df46"
],
"permissions": [
{
"uid": "019619df-4767-730f-8d31-143712a08141"
},
{
"uid": "019619df-4767-730f-8d31-143712a08142"
}
]
}
```
Required fields:
- `email`: User's email address
- `firstName`: User's first name
- `lastName`: User's last name
- `groupUids`: Array of group UIDs
- `permissionUids`: Array of permission UIDs
Possible responses:
- 200: User created successfully
### Notes
- All email addresses must be unique in the system
- The `password`, if defined, must be at least 8 characters long and contain at least one uppercase letter, one lowercase letter, one number and one special character.
- If `fromExternalIdp` is true, the user must authenticate through their identity provider
- Asset access can be granted to all assets (`"allAssets": true`) or specific assets using the `permissions.uid` field
- Permission UIDs must be valid and retrieved from the permissions endpoint
### Error Handling
Common error scenarios:
- Duplicate email address
- Invalid permission UIDs
- Missing required fields
Always check the response status and handle errors appropriately in your implementation.
---
# Modifying a user
URL: https://docs.stonal.io/md/users/legacy/modify-a-user
# Modifying a user
This guide explains how to update an existing user in the Stonal platform using the API. Similar to user creation, updating a user requires gathering current information before making changes.
:::info Prerequisites
- API access credentials
- A valid access token (see: [**Get a token**](/api/get-access-token))
- An organization code
:::
:::note Update by email
If you wish to update an existing user by email, you can follow the previous creation process, as described: [Here](/md/users/legacy/create-a-user)
The creation endpoint will update the user if it already exists.
:::
### Step 1: Lookup the user information that needs change
Verify available companies that can be assigned to the user:
See: [**API Specification**](/api/legacy/find-all-companies-by)
```http
GET /v1/organizations/{organizationCode}/users/companies
```
Query parameters:
- `page` (optional): Page number (default: 0)
- `size` (optional): Items per page (default: 100)
The resulting companies can be used to update a user with the `company` field.
Thus giving the user the right to access the assets of the corresponding company.
Retrieve available authorization models (application groups):
See: [**API Specification**](/api/legacy/find-all-authorizations-by)
```http
GET /v1/organizations/{organizationCode}/users/authorizations
```
This returns available authorization models (application groups) with:
- Model ID
- Name
- Description
- Available applications
The resulting authorization models can be used to update a user with the `authorization.modelId` field.
Thus giving the user the right to access the corresponding applications.
Get available user groups:
See: [**API Specification**](/api/legacy/find-all-groups-by)
```http
GET /v1/organizations/:organizationCode/users/groups
```
The resulting groups can be used to create a user with the `groupUids` field.
Retrieve available user profiles:
See: [**API Specification**](/api/legacy/find-all-profiles-by)
```http
GET /v1/organizations/{organizationCode}/users/profiles
```
The resulting profiles can be used to update a user with the `profile` field.
Get the current list of available permissions:
See: [**API Specification**](/api/legacy/find-all-permissions-by)
```http
GET /v1/organizations/{organizationCode}/users/permissions?pageNumber=1&pageSize=10
```
Query parameters:
- `pageNumber` (required): Page number
- `pageSize` (required): Items per page
- `type` (optional): Permission type (e.g., ASSET)
- `subType` (optional): Permission sub-type (e.g., PORTFOLIO)
- `q` (optional): Search query on label
- `sortField` (optional): Field to sort by
- `sortOrder` (optional): Sort order (asc/desc)
The resulting permissions can be used to update a user with the `permissionUids` field.
Thus giving the user the right to access the corresponding assets.
### Step 2: Update the user
Update the user with the modified information:
See: [**API Specification**](/api/legacy/create)
```http
PUT /v1/organizations/{organizationCode}/users/{id}
```
Request body:
```json
{
"firstName": "updatedFirstName",
"lastName": "updatedLastName",
"company": "updatedCompanyName",
"groupUids": ["0192d7b7-2994-7ad5-9952-26862f33c21a"],
"authorization": {
"modelId": "2b2e8a4b-bfbd-4c56-b8d6-c8cb1d8c58ba",
"profile": "Developer",
"asset": {
"all": false
}
},
"permissionUids": [
"0192d7b7-2994-7ad5-9952-26862f33c21a",
"0192d7b7-7073-7e58-896c-07113f22363a"
]
}
```
Required fields:
- `firstName`: User's first name
- `lastName`: User's last name
- `company`: Company name (must be from available companies)
- `groupUids`: Array of group UIDs
- `authorization`: Authorization configuration
- `modelId`: ID of the authorization model
- `profile`: User profile
- `asset`: Asset access configuration
- `permissionUids`: Array of permission UIDs
Note: Unlike creation, the update request doesn't include the email address as it cannot be modified.
Possible responses:
- 204: User updated successfully
- 404: User not found
### Key Differences from Creation
When updating a user, note these important differences:
1. Email address cannot be modified
2. Use PUT instead of POST method
3. Requires user ID in the path
4. Returns 204 (no content) on success instead of 201
### Important Considerations
1. **Partial Updates**: The API does not support partial updates. You must provide all required fields in the update request, even if you're only changing one value.
2. **Asset Access**:
- When modifying asset access, provide the complete new set of asset codes
- Setting `"all": true` overrides any specific permissions for the asset type
3. **Permissions**:
- Provide the complete new set of permission UIDs
- All existing permissions will be replaced
- All permission UIDs must be valid
4. **Authorization Model**:
- Changing the authorization model might affect user's access to various features
- Ensure the new model provides appropriate access levels
### Error Handling
Common error scenarios:
- User not found (404)
- Invalid authorization model ID
- Invalid company name
- Invalid group UIDs
- Invalid permission UIDs
- Missing required fields
- Invalid format of asset codes
### Best practices
1. Always retrieve current user data before updating
2. Validate all new values against the lookup endpoints
3. Keep track of sensitive changes (authorization, permissions)
4. Implement proper error handling for 404 responses
---
# OpenID Connect
URL: https://docs.stonal.io/md/auth/openid
# OpenID Connect
## Obtaining Client Credentials
A client defines the application authorized to generate tokens for users.
To request your client credentials, contact us at [api-support@stonal.com](mailto:api-support@stonal.com).
## Creating an API User
:::info Prerequisites
You must have admin account access.
:::
1. Navigate to the [**User Management Interface**](https://app.stonal.io/users)
2. Create an api user account
3. Store the password securely - you'll need it for token generation
4. You may log out after completing this step

## Retrieving an Access Token
Make a POST request to:
```
POST https://sso.stonal.io/realms/stonal/protocol/openid-connect/token
```
Headers:
```
Content-Type: application/x-www-form-urlencoded
Authorization: Basic
```
URL-encoded parameters:
```
grant_type=password&username=&password=
```
You'll receive a response similar to:
```json
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0bWxIZkFFR1ItN2RkOTZyR205SjQ0NlRpQlVNZFdFNE00Uk1ETFIxUE44In0.eyJleHAiOjE3Mzk4OTYyMDgsImlhdCI6MTczOTg5NTkwOCwianRpIjoiYTEwZTg5NmMtODc1NC00NGI4LWEwZDEtZTNkZjIyZmVjNGIyIiwiaXNzIjoiaHR0cHM6Ly9zc28uc3RvbmFsLWRldi5pby9yZWFsbXMvc3RvbmFsIiwiYXVkIjpbInJlYWxtLW1hbmFnZW1lbnQiLCJhY2NvdW50Il0sInN1YiI6ImNkNjEzMDlmLWU0NTQtNGVjYy1hNThlLTMxNDU2MmZlYmQyOSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImF1dGhvcml6YXRpb24tc2VydmVyIiwic2lkIjoiNGM1YTQ4YTMtMjBlYi00ZWQ0LWIwZjMtNjBiNzQ5MWY3YzMyIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLXN0b25hbCIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJyZWFsbS1tYW5hZ2VtZW50Ijp7InJvbGVzIjpbImltcGVyc29uYXRpb24iXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBvcGVuaWQgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwibmFtZSI6ImFkbWluIGFkbWluIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWRtaW4tcGxhdGVmb3JtZUBsYWZvbmNpZXJlbnVtZXJpcXVlLmNvbSIsImdpdmVuX25hbWUiOiJhZG1pbiIsImZhbWlseV9uYW1lIjoiYWRtaW4iLCJlbWFpbCI6ImFkbWluLXBsYXRlZm9ybWVAbGFmb25jaWVyZW51bWVyaXF1ZS5jb20ifQ.A8K_CXFRG7MdXiMoQhHv4flsomkMlI8OVZ_siC6BwAIDg19Q-LzfMjSbGZuwwx_k-UIeFAfrOvsEK1Pcr5QSVFsnJ-cThr4abaKIXGDOh11QUisM7ZTmk5iAWkoIOOni94Q4WgWscoZxgEOqtXGJsmpx9xipTXCNfW_nA_okquMmJLdm3zzHqAaq0uD7LmMavxWYQfyG0OT8NB-Xm7baiZgL_BdQnILjbZ2eeoJJPf0fv5i_tuyoOyC3NbOMMrBhzJd187KUpQx0lODhprT53DY2DMth-44evdCjBiQsw_BHI9HPT_qr7BBO_8EbbBQVZF3ny5RrI4cUxhsm_xhODQ",
"expires_in": 300,
"refresh_expires_in": 172800,
"refresh_token": "eyJhbGciOiJIUzUxMiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI4ODZkZDRlMi1hNzUwLTQ5NmQtODhiYy04YmM1MGM1OTViOTEifQ.eyJleHAiOjE3NDAwNjg3MDgsImlhdCI6MTczOTg5NTkwOCwianRpIjoiYmEzYzczM2EtM2Y2OS00YzEwLTgzMDYtYzcwMTdlODFkOTZmIiwiaXNzIjoiaHR0cHM6Ly9zc28uc3RvbmFsLWRldi5pby9yZWFsbXMvc3RvbmFsIiwiYXVkIjoiaHR0cHM6Ly9zc28uc3RvbmFsLWRldi5pby9yZWFsbXMvc3RvbmFsIiwic3ViIjoiY2Q2MTMwOWYtZTQ1NC00ZWNjLWE1OGUtMzE0NTYyZmViZDI5IiwidHlwIjoiUmVmcmVzaCIsImF6cCI6ImF1dGhvcml6YXRpb24tc2VydmVyIiwic2lkIjoiNGM1YTQ4YTMtMjBlYi00ZWQ0LWIwZjMtNjBiNzQ5MWY3YzMyIiwic2NvcGUiOiJiYXNpYyByb2xlcyBhY3IgcHJvZmlsZSB3ZWItb3JpZ2lucyBvcGVuaWQgZW1haWwifQ.E9kEudbh2CVVRxlXly6UA15cX6VYhyJ4_appoA-Bjw73OZ7RbW8TemhntqBC--JHuoWCIPp5HGT7oSywySMPNA",
"token_type": "Bearer",
"not-before-policy": 0,
"session_state": "4c5a48a3-20eb-4ed4-b0f3-60b7491f7c32",
"scope": "profile openid email"
}
```
#### Using Your Access Token
Include the token in the `Authorization` header of all API requests using the `Bearer` prefix:
```
Authorization: Bearer
```
For example:
```
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI0bWxIZkFFR1ItN2RkOTZyR205SjQ0NlRpQlVNZFdFNE00Uk1ETFIxUE44In0.eyJleHAiOjE3Mzk4OTYyMDgsImlhdCI6MTczOTg5NTkwOCwianRpIjoiYTEwZTg5NmMtODc1NC00NGI4LWEwZDEtZTNkZjIyZmVjNGIyIiwiaXNzIjoiaHR0cHM6Ly9zc28uc3RvbmFsLWRldi5pby9yZWFsbXMvc3RvbmFsIiwiYXVkIjpbInJlYWxtLW1hbmFnZW1lbnQiLCJhY2NvdW50Il0sInN1YiI6ImNkNjEzMDlmLWU0NTQtNGVjYy1hNThlLTMxNDU2MmZlYmQyOSIsInR5cCI6IkJlYXJlciIsImF6cCI6ImF1dGhvcml6YXRpb24tc2VydmVyIiwic2lkIjoiNGM1YTQ4YTMtMjBlYi00ZWQ0LWIwZjMtNjBiNzQ5MWY3YzMyIiwiYWNyIjoiMSIsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJkZWZhdWx0LXJvbGVzLXN0b25hbCIsIm9mZmxpbmVfYWNjZXNzIiwidW1hX2F1dGhvcml6YXRpb24iXX0sInJlc291cmNlX2FjY2VzcyI6eyJyZWFsbS1tYW5hZ2VtZW50Ijp7InJvbGVzIjpbImltcGVyc29uYXRpb24iXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBvcGVuaWQgZW1haWwiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwibmFtZSI6ImFkbWluIGFkbWluIiwicHJlZmVycmVkX3VzZXJuYW1lIjoiYWRtaW4tcGxhdGVmb3JtZUBsYWZvbmNpZXJlbnVtZXJpcXVlLmNvbSIsImdpdmVuX25hbWUiOiJhZG1pbiIsImZhbWlseV9uYW1lIjoiYWRtaW4iLCJlbWFpbCI6ImFkbWluLXBsYXRlZm9ybWVAbGFmb25jaWVyZW51bWVyaXF1ZS5jb20ifQ.A8K_CXFRG7MdXiMoQhHv4flsomkMlI8OVZ_siC6BwAIDg19Q-LzfMjSbGZuwwx_k-UIeFAfrOvsEK1Pcr5QSVFsnJ-cThr4abaKIXGDOh11QUisM7ZTmk5iAWkoIOOni94Q4WgWscoZxgEOqtXGJsmpx9xipTXCNfW_nA_okquMmJLdm3zzHqAaq0uD7LmMavxWYQfyG0OT8NB-Xm7baiZgL_BdQnILjbZ2eeoJJPf0fv5i_tuyoOyC3NbOMMrBhzJd187KUpQx0lODhprT53DY2DMth-44evdCjBiQsw_BHI9HPT_qr7BBO_8EbbBQVZF3ny5RrI4cUxhsm_xhODQ
```
:::info
Important: Access tokens expire after 5 minutes. After expiration, you must either:
1. Generate a new access token, or
2. Use the refresh token to obtain a new access token
:::
---
# Upload a file
URL: https://docs.stonal.io/md/documents/upload-a-file
This guide covers the supported strategies for uploading documents to the Stonal platform. It details the main combinations you can use to tell the system where and how to place your document during upload.
---
### Prerequisites
* A valid OAuth access token (see [Authentication](/md/auth))
* Your organization code (used in the endpoint path)
* A file to upload
---
## 1. Upload Strategies & Required Fields
Each strategy maps to a combination of manifest fields. Use the table below to identify which payload suits your scenario.
| Strategy | Required Manifest Fields | Description |
| --- | --- | --- |
| **1. Asset UID + Documentation Template** โญ | `asset.uid`, `documentation.template` | **Recommended**: let the platform resolve the correct documentation for the asset. |
| **2. External Asset Code + Documentation Template** โญ | `asset.externalIds.{source}`, `documentation.template` | **Recommended** when your integration does not use Stonal asset UIDs. |
| **3. Asset + Documentation Template + Folder Template** โญ | Strategy 1 or 2 + `folder.template` | **Recommended** when you want to target a specific folder type within the documentation. |
| **4. Documentation UID** | `documentation.uid` | Use this when you already know the existing documentation you want to upload into. |
`linkedAssets`, `tags.documentation` and `documentClass` are optional enrichments that can be added on top of these strategies.
---
## 2. Manifest Example Payloads
Include only the fields you need for your chosen strategy.
### 2.1 By Asset UID + Documentation Template โญ
```json
{
"asset": {
"uid": "822d8c50-0deb-4dae-8def-c9f9eab7f5be"
},
"documentation": {
"template": "f90ee77d-b6cc-41fa-8d1f-bea649e6d93d"
}
}
```
### 2.2 By External Asset Code + Documentation Template โญ
```json
{
"asset": {
"externalIds": {
"code": "BUILDING_001"
}
},
"documentation": {
"template": "f90ee77d-b6cc-41fa-8d1f-bea649e6d93d"
}
}
```
### 2.3 By Asset + Documentation Template + Folder Template โญ
```json
{
"asset": {
"externalIds": {
"code": "BUILDING_001"
}
},
"documentation": {
"template": "f90ee77d-b6cc-41fa-8d1f-bea649e6d93d"
},
"folder": {
"template": "e8df7c2a-b5bb-41fa-8d1f-bea649e6d93d"
}
}
```
### 2.4 By Documentation UID
Use this when the documentation UID is already known, for example because you previously retrieved it from the asset documentations endpoint.
```json
{
"documentation": {
"uid": "62c42614-31a8-43bb-a76f-8499c1b868e0"
}
}
```
### 2.5 Add linked assets
`linkedAssets` can be combined with the recommended template-based strategies.
```json
{
"asset": {
"externalIds": {
"code": "BUILDING_001"
}
},
"documentation": {
"template": "f90ee77d-b6cc-41fa-8d1f-bea649e6d93d"
},
"linkedAssets": [
{ "code": "RU-001" },
{ "uid": "fac98b98-c8ba-47e7-b5a6-2bc5222ae402" }
]
}
```
Important notes:
- `linkedAssets` by `code` are resolved relative to the parent `asset`.
- If you send `linkedAssets` without an `asset`, linked assets may be ignored.
- Use the list format shown above for new integrations.
### 2.6 Add documentation tags and a document class
Documentation tags are only associated if they already exist on the target documentation. The upload API does not create tags on the fly.
```json
{
"asset": {
"uid": "822d8c50-0deb-4dae-8def-c9f9eab7f5be"
},
"documentation": {
"template": "f90ee77d-b6cc-41fa-8d1f-bea649e6d93d"
},
"documentClass": "123e4567-e89b-12d3-a456-426614174007",
"tags": {
"documentation": ["LEASE 2026", "SIGNED"]
}
}
```
---
## 3. Template Discovery (Recommended Workflow)
Before using template-based strategies, discover available templates:
### 3.1 List available templates
```bash
curl -H "Authorization: Bearer {accessToken}" \
"https://api.stonal.io/document-storage/v1/organizations/{organizationCode}/templates?language=fr-FR"
```
**Response:**
```json
{
"templates": [
{
"id": "f90ee77d-b6cc-41fa-8d1f-bea649e6d93d",
"name": "DOE",
"organizationCode": "STONAL",
"attachmentType": "BUILDING_GROUP"
}
]
}
```
### 3.2 Get template details with folders
```bash
curl -H "Authorization: Bearer {accessToken}" \
"https://api.stonal.io/document-storage/v1/organizations/{organizationCode}/templates/{templateId}?language=fr-FR"
```
**Response:**
```json
{
"template": {
"id": "f90ee77d-b6cc-41fa-8d1f-bea649e6d93d",
"name": {
"fr-FR": "DOE"
},
"organizationCode": "STONAL",
"attachmentType": "BUILDING_GROUP",
"folders": [
{
"id": "e8df7c2a-b5bb-41fa-8d1f-bea649e6d93d",
"name": {
"fr-FR": "Documents gรฉnรฉraux"
},
"parentId": null,
"documentClass": {
"identifier": "documentClassIdentifier",
"code": "DPE"
}
}
]
}
}
```
You can then use:
- `template.id` as `documentation.template`
- `template.folders[].id` as `folder.template`
### 3.3 Retrieve existing documentations for an asset
If you want to upload into an existing documentation, first retrieve the documentations attached to the asset:
```bash
curl -H "Authorization: Bearer {accessToken}" \
"https://api.stonal.io/document-storage/v1/organizations/{organizationCode}/assets/{assetId}/documentations"
```
Use the returned `identifier` as `documentation.uid`.
For the full retrieval workflow, see [Retrieve documentations for an asset](/md/documents/apidocumentation/get-documentations).
---
## 4. Single-Request API Call
Send both your manifest and file in one `multipart/form-data` POST:
```bash
curl -X POST \
"https://api.stonal.io/document-storage/v1/organizations/{organizationCode}/files/upload" \
-H "Authorization: Bearer {accessToken}" \
-F 'manifest=@manifest.json;type=application/json' \
-F 'file=@/path/to/your/file.pdf;type=application/pdf'
```
Replace placeholders with your actual values.
---
## 5. Response Format
**200 OK** with JSON:
```json
{
"documentId": "123e4567-e89b-12d3-a456-426614174000",
"duplicateDocumentIds": []
}
```
The API may also return non-blocking warnings in the `X-DOCUMENT-STORAGE-WARNING` response header.
Examples:
- `LINKED_ASSETS_NOT_FOUND`
- `LINKED_ASSETS_NOT_FOUND:RU-001,PARK-002`
- `LINKED_ASSETS_SKIPPED_NO_PARENT`
### When is AI processing triggered?
In public upload flows, an AI request is sent when the uploaded file is attached to a documentation context.
The requested treatment then depends on the upload context:
- if the file is uploaded only into a documentation, AI classification is requested
- if a document class is provided, metadata extraction is requested
- OCR is always requested as part of the AI request
In practice, template-based uploads remain the main public way to place the document in the right documentation before AI processing starts.
---
## 6. Error Handling
* **400 Bad Request**: Missing or invalid manifest fields, or invalid multipart request
* **404 Not Found**: Referenced asset, documentation, template folder, or matching documentation for the provided asset and documentation template was not found
* **409 Conflict**: Specified asset and documentation are not linked
Handle these responses gracefully in your integration.
---
## 7. Best Practices
* **Prioritize templates**: This is the main public integration path and the most future-proof one.
* **Discover templates first**: Use the `/templates` API to explore available options.
* **Prefer asset UIDs when possible**: They remain the clearest identifiers.
* **Use documentation UID only when already known**: Retrieve it first from the asset documentations endpoint.
* **Use linked assets only with a parent asset**: Code-based linked assets are resolved relative to that parent.
* **Remember that documentation tags are not created automatically**: Only existing documentation tags are associated.
For more details, see our [API specification](/api/documents/upload-file).
---
## 8. Complete Example
### 8.1 Recommended approach
For this manifest example:
```json title="manifest.json"
{
"asset": {
"externalIds": {
"code": "BUILDING_001"
}
},
"documentation": {
"template": "f90ee77d-b6cc-41fa-8d1f-bea649e6d93d"
},
"folder": {
"template": "e8df7c2a-b5bb-41fa-8d1f-bea649e6d93d"
},
"linkedAssets": [
{ "code": "RU-001" }
],
"tags": {
"documentation": ["DPE 2026"]
},
"documentClass": "123e4567-e89b-12d3-a456-426614174007"
}
```
We will use the following command to upload the file:
```bash
curl \
-X POST "https://api.stonal.io/document-storage/v1/organizations/{organizationCode}/files/upload" \
-H "Authorization: Bearer {accessToken}" \
-F 'manifest=@manifest.json;type=application/json' \
-F 'file=@sample.pdf;type=application/pdf'
```
### 8.2 Existing documentation already known
For this manifest example:
```json title="manifest.json"
{
"documentation": {
"uid": "62c42614-31a8-43bb-a76f-8499c1b868e0"
}
}
```
We will use the same upload command with that manifest.
---
# Deleting a user
URL: https://docs.stonal.io/md/users/legacy/delete-a-user
# Deleting a user
This guide walks through the process of deleting an existing user from the Stonal platform using the API.
:::info Prerequisites
- A valid set of API credentials
- A valid access token (see: [**Get a token**](/api/get-access-token))
- An organization code
:::
### Step 1: Retrieving the user UID
Before deleting a user, verify that they exist to ensure the correct user will be deleted.
See: [**API Specification**](/api/legacy/search-user-on-organization)
```http
POST /v1/organizations/{organizationCode}/users/search
```
Request body:
```json
{
"email": "firstName.lastName@corp.com"
}
```
If the user exists, you'll receive a 200 response with the user's details including their ID. If not, you'll need to verify the user information.
### Step 2: Delete the user
Once you have confirmed the user's existence and have their ID, you can proceed with deletion.
See: [**API Specification**](/api/legacy/delete)
```http
DELETE /v1/organizations/{organizationCode}/users/{id}
```
Path parameters:
- `organizationCode`: Code of client (e.g., "STONAL")
- `id`: ID of user to delete (e.g., "123456789")
Possible responses:
- 204: User has been successfully deleted
- 404: User to delete does not exist
### Notes
- The deletion operation is permanent and cannot be undone
- You must have appropriate authorization to delete users
- The user ID must be obtained from a previous search or get user operation
- All user permissions and authorizations will be removed upon deletion
### Error Handling
Common error scenarios:
- User UID not found
- Invalid organization code
- Insufficient permissions to delete users
- Invalid or expired access token
- Network connectivity issues
Always check the response status and handle errors appropriately in your implementation.
---
# Modifying a user
URL: https://docs.stonal.io/md/users/modify-a-user
# Modifying a user
This guide explains how to update an existing user in the Stonal platform using the API. Similar to user creation, updating a user requires gathering current information before making changes.
:::info Prerequisites
- API access credentials
- A valid access token (see: [**Get a token**](/md/auth))
- An organization code
:::
:::note Update by email
If you wish to update an existing user by email, you can follow the previous creation process, as described: [Here](/md/users/create-a-user)
The creation endpoint will update the user if it already exists.
:::
### Step 1: Lookup the user information that needs change
#### User groups
Get available user groups:
See: [**API Specification**](/api/users/get-groups)
```http
GET /v1/organizations/:organizationCode/groups?pageNumber=1&pageSize=10
```
The resulting groups can be used to update a user with the `groupUids` field.
#### Application groups
Retrieve available application groups:
See: [**API Specification**](/api/users/get-permissions)
```http
GET /v1/organizations/:organizationCode/users/permissions?pageNumber=1&pageSize=10&type=SCOPE_GROUP&subType=APPLICATION
```
The resulting application groups can be used to update a user with the `permissions.uid` field.
Thus giving the user the right to access the corresponding applications.
#### Profiles
Get available user profiles:
See: [**API Specification**](/api/users/get-permissions)
```http
GET /v1/organizations/:organizationCode/users/permissions?pageNumber=1&pageSize=10&type=PROFILE
```
The resulting profiles can be used to update a user with the `permissions.uid` field.
#### Assets
Here is the list of currently available `ASSET` subtypes:
- `PORTFOLIO`
- `COMPANY`
- `FACILITY`
- `BUILDING_GROUP`
- `BUILDING`
##### Companies
See: [**API Specification**](/api/users/get-permissions)
```http
GET /v1/organizations/:organizationCode/users/permissions?pageNumber=1&pageSize=10&type=ASSET&subType=COMPANY
```
The resulting permissions can be used to update a user with the `permissions.uid` field.
Thus giving the user the right to access the corresponding companies.
##### Portfolios
See: [**API Specification**](/api/users/get-permissions)
```http
GET /v1/organizations/:organizationCode/users/permissions?pageNumber=1&pageSize=10&type=ASSET&subType=PORTFOLIO
```
The resulting permissions can be used to update a user with the `permissions.uid` field.
Thus giving the user the right to access the corresponding portfolios.
##### Report groups
See: [**API Specification**](/api/users/get-permissions)
```http
GET /v1/organizations/:organizationCode/users/permissions?pageNumber=1&pageSize=10&type=REPORT_GROUP
```
The resulting permissions can be used to update a user with the `permissions.uid` field.
Thus giving the user the right to access the underlying reports.
### Step 2: Update the user
Update the user with the modified information:
See: [**API Specification**](/api/users/update-user)
```http
PUT /v2/organizations/{organizationCode}/users/{uid}
```
Request body:
```json
{
"email": "firstName.lastName@corp.com",
"firstName": "firstName",
"lastName": "lastName",
"password": "SecureP@ssw0rd!",
"fromExternalIdp": false,
"allAssets": false,
"userGroupUids": [
"019619df-4768-76b7-81e3-2c56d374df46"
],
"permissions": [
{
"uid": "019619df-4767-730f-8d31-143712a08141"
},
{
"uid": "019619df-4767-730f-8d31-143712a08142"
}
]
}
```
Required fields:
- `email`: User's email address
- `firstName`: User's first name
- `lastName`: User's last name
- `groupUids`: Array of group UIDs
- `permissionUids`: Array of permission UIDs
Possible responses:
- 200: User updated successfully
- 404: User not found
### Key Differences from Creation
When updating a user, note these important differences:
1. Email address cannot be modified
2. Use PUT instead of POST method
3. Requires user UID in the path
### Important Considerations
1. **Partial Updates**: The API does not support partial updates. You must provide all required fields in the update request, even if you're only changing one value.
2. **Permissions**:
- Provide the complete new set of permission UIDs
- All existing permissions will be replaced
- All permission UIDs must be valid
### Error Handling
Common error scenarios:
- User isn't found (404)
- Invalid group UIDs
- Invalid permission UIDs
- Missing required fields
### Best practices
1. Always retrieve current user data before updating
2. Validate all new values against the lookup endpoints
3. Keep track of sensitive changes (permissions)
4. Implement proper error handling for 404 responses
---
# Search Documents
URL: https://docs.stonal.io/md/documents/search-documents
# Search Documents
This guide covers **all** supported filters for querying documents in the Stonal platform. It details every filter you can use to refine your search queries.
---
### Prerequisites
* A valid OAuth access token (see [Authentication](/md/auth))
* Your organization code (used in the endpoint path)
---
## 1. Filter Groups & Fields
Each group represents a set of filters targeting different aspects of your documents.
### 1.1 Filing Location Filters
| Filter | Type | Description |
| -------------------------- | ------------ | --------------------------------------------------------- |
| `assetIdentifiers` | Set\[String] | Only return documents attached to these asset UIDs. |
| `documentationIdentifiers` | Set\[String] | Only return documents linked to these documentation UIDs. |
| `folderIdentifiers` | Set\[String] | Only return documents stored in these folder UIDs. |
### 1.2 Business Context Filters
| Filter | Type | Description |
| ----------------------- | ------------ | ------------------------------------------------------------------------------------------ |
| `linkedAssetIdentifiers` | Set\[String] | Documents related to specific linked assets (e.g., building, apartments). |
| `tenantIdentifiers` | Set\[String] | Documents belonging to or created by specific tenants. |
| `linkedAsset` | String | Partial or full name match of the linked assetโuseful when you donโt have the UIDs on hand. |
| `hasLinkedAssets` | Boolean | Filter documents that have at least one linked asset (`true`) or none (`false`). |
### 1.3 Basic Document Filters
| Filter | Type | Description |
| ---------------- | ------------ | ----------------------------------------------------------|
| `identifier` | Set\[String] | Exact document UIDs to retrieve. |
| `name` | String | Partial or full name match (case-insensitive). |
| `documentStatus` | String | One of `CLASSIFICATION`, `METADATA_EXTRACTION`, `COMPLETED`. |
| `hashSha256` | String | SHA-256 checksum of the documentโs contents. |
| `updatedAfter` | String | ISO-8601 timestamp to filter documents updated during or after this date. |
### 1.4 Metadata Filters
| Filter | Type | Description |
| ------------- | ------------------- | ------------------------------------------------------------------- |
| `tags` | Set\[String] | Documents tagged with one or more specified tags. |
| `properties` | Map\[String,String] | Documents with matching metadata key-value pairs. |
| `propertyKey` | String | Documents containing a specific metadata key (regardless of value). |
| `hasMetadata` | Boolean | Filter documents with exploitable metadata (`true`) or none (`false`). |
### 1.5 Folder & Classification Filters
| Filter | Type | Description |
| --------------------- | ------------------ | ---------------------------------------------- |
| `parentFolderName` | String | Partial match on the parent folderโs name. |
| `predictedFolderName` | String | AI-predicted folder name after classification. |
| `folder` | `{ name, locale }` | Match folder by exact name and locale. |
| `documentClass` | `{ name, locale }` | Match classification class by name and locale. |
---
## 2. Search Document API Call
Send your query in one JSON payload:
```bash
curl -X POST "https://api.stonal.io/document-storage/v1/organizations/{organizationCode}/documents/search" \
-H "Authorization: Bearer {accessToken}" \
-H "Content-Type: application/json" \
-d '{
"name": "Invoice",
"tags": ["important","finance"],
"folderIdentifiers": ["folder-123"],
"assetIdentifiers": ["asset-456"],
"linkedAsset": "Unit A",
"pageNumber": 1,
"pageSize": 100,
"sortOrder": "DESC",
"columnToSort": "creationDate",
}'
```
Pagination is **1-based**: use `pageNumber: 1` for the first page.
---
## 3. Response Format
**200 OK** with JSON:
```json
{
"result": [],
"total": 42,
"pageable": {
"pageNumber": 1,
"pageSize": 100,
"sort": {}
}
}
```
`pageable.pageNumber` is also **1-based** in responses.
---
## 4. Error Handling
* **400 Bad Request**: Missing or invalid filters
* **401 Unauthorized**: Invalid or missing access token
* **403 Forbidden**: Missing `stonal.document.read` scope
* **404 Not Found**: `organizationCode` not recognized
---
## 5. Best Practices
* **Use UIDs whenever possible** (`identifier`, `assetIdentifiers`) for precise, performant queries.
* **Combine multiple filters** to narrow down results (e.g., status + metadata + folder).
* **Leverage `linkedAsset`** for human-friendly searches when you donโt have exact UIDs.
* **Tag and property filters** are ideal for contextual grouping across documents.
For full API reference, see the [Stonal API specification](/api/documents/search).
---
# Searching data
URL: https://docs.stonal.io/md/assets/search
# Searching data
This guide explains how to fetch the right kind of data depending on your usage, by using the Stonal search API with filters, pagination, and advanced search patterns.
:::info
You will need to get a personal access token to use the search API.
:::
## Getting started
### Authentication
Here is a sample request around [the search API](/api/assets/search-assets), where we will fetch `BUILDING_GROUP`s and `BUILDING`s.
You need to pass a query with something like that:
```bash title="๐ search.sh"
#!/bin/sh -ex
# Your organization
STONAL_ORG=DEMO
# Fetch your token from here: https://app.stonal.io/users/app
STONAL_TOKEN=9631e269-e21e-4ce6-92a9-c6f7ed1fa98a
curl \
https://api.stonal.io/datalake/v1/organizations/$STONAL_ORG/assets/search \
-v \
-X POST \
-H 'Content-Type: application/json' \
-H "Authorization: Bearer $STONAL_TOKEN" \
-d @query.json -o output.json
```
### Initial query
```json title="๐ query.json"
{
"search": {
"type": {
"equals": [
"BUILDING",
"BUILDING_GROUP"
]
}
},
"properties": {
"simple": ["LAT", "LNG"]
},
"fields": [
"name",
"type",
"uid",
"externalIds"
],
"paginate": {
"size": 2
}
}
```
The output will be similar to this:
```json title="๐ output.json"
{
"assets": [
{
"uid": "f6472f1a-9473-4fbf-9e1c-1c063eebb430",
"name": "Building Group 1",
"type": "BUILDING_GROUP",
"externalIds": {
"SOURCE_NAME": "BG001"
},
"properties": {
"simple": {
"LAT": "46.800",
"LNG": "1.6959"
}
}
},
{
"uid": "b81ce341-86f0-4cfe-a806-1d4a0f9a12ed",
"name": "Building 1",
"type": "BUILDING",
"externalIds": {
"SOURCE_NAME": "B001"
},
"properties": {
"simple": {
"LAT": "46.500",
"LNG": "1.7959"
}
}
}
],
"paginate": {
"size": 2,
"cursor": "123456"
}
}
```
### Subsequent queries
Then to fetch the next batch of 10 assets, you shall issue the same query with the `paginate.cursor` set to
the received value, ie `"123456"`.
```json title="๐ query.json"
{
"search": {
"type": {
"equals": [
"BUILDING",
"BUILDING_GROUP"
]
}
},
"fields": [
"name",
"type",
"uid",
"externalIds",
"parent"
],
"paginate": {
"size": 2,
"cursor": "123456"
}
}
```
The output will be similar to this:
```json title="๐ output.json"
{
"assets": [
{
"uid": "dd127caf-5b9b-4116-884e-b42505ac1bd6",
"name": "Building Group 2",
"type": "BUILDING_GROUP",
"externalIds": {
"CODE": "BG002"
}
},
{
"uid": "2c2ca7be-78f1-4734-a4c6-bbc773c9027b",
"name": "Building 2",
"type": "BUILDING",
"externalIds": {
"CODE": "B002"
},
"parent": {
"uid": "dd127caf-5b9b-4116-884e-b42505ac1bd6"
}
}
],
"paginate": {
"size": 2,
"cursor": "234567"
}
}
```
## Going further
There are many other ways to query data. And you can mix any of these filters.
### Searching by external IDs
You can search using a single external ID, either from the asset itself or from its relations.
Searching a specific asset by one of its external IDs.
```json title="๐ by asset's external ID"
"search": {
"asset": {
"externalIds": {
"SOURCE_NAME": "BG001"
}
}
}
```
Searching assets whose parent matches the given external ID.
```json title="๐ by the external ID of assetโs parent"
"search": {
"parent": {
"externalIds": {
"SOURCE_NAME": "BG001"
}
}
}
```
### Navigating the hierarchy
Assets are organized in a hierarchy (see [Assets hierarchy](/md/assets/hierarchy)). You can use the following relation filters to navigate up and down the tree. Only one relation filter can be used per request.
#### Find direct children of a parent
Use `parent` to find all assets that are direct children of a given parent asset.
```json title="๐ direct children of a building group"
"search": {
"parent": { "uid": "dd127caf-5b9b-4116-884e-b42505ac1bd6" }
}
```
#### Find all descendants of an ancestor
Use `parentRecursive` to find all assets nested under a given ancestor at any depth.
```json title="๐ all descendants of a building group (buildings, plans, levels, spaces, equipments...)"
"search": {
"parentRecursive": { "externalIds": { "CODE": "SITE_001" } }
}
```
#### Find direct parents of a child
Use `child` to find assets that are direct parents of a given child asset.
```json title="๐ direct parents of a building"
"search": {
"child": { "uid": "b81ce341-86f0-4cfe-a806-1d4a0f9a12ed" }
}
```
#### Find all ancestors of a descendant
Use `childRecursive` to find all ancestor assets of a given descendant at any depth. This is useful for walking up the hierarchy from a leaf asset (e.g. equipment) to find its building, building group, etc.
```json title="๐ find the building of an equipment"
{
"search": {
"childRecursive": { "uid": "$equipmentUid" },
"type": { "equals": ["BUILDING", "BUILDING_SECTION"] }
},
"fields": ["name", "type"],
"paginate": { "size": 10 }
}
```
You can also use `externalIds` to identify the descendant:
```json title="๐ find all ancestors of an equipment by external ID"
"search": {
"childRecursive": {
"externalIds": { "TRIRIGA": "EQP1008473" }
}
}
```
:::note
`childRecursive` returns all ancestors but excludes the queried child asset itself from the results. All other filters (`type`, `name`, `fields`, `properties`, `paginate`) apply normally on the returned ancestors.
:::
### Matching on a pattern
You can search for types matching a regex pattern.
#### Searching for spaces
:::note
`SPACE` is another name for rooms in the Stonal platform.
:::
This will return assets of `SPACE` and `SPACE|*` types:
```json title="๐ asset type pattern"
"search": {
"type": {
"matches": [
"SPACE",
"SPACE\\|.*"
]
}
}
```
Please note that this will apply to all spaces within the organization. If you want to limit it to a particular building, you should combine it with a parent filter like this:
```json title="๐ spaces within a specific building group"
"search": {
"type": {
"matches": [
"SPACE",
"SPACE\\|.*"
]
},
"parent": {
"externalIds": {
"SOURCE_NAME": "BG001"
}
}
}
```
### Excluding by type
:::info
Only one of `notMatches` or `notEquals` is accepted (same as `matches` & `equals`)
:::
#### By matching on a pattern
Exclude specified equipments _(those having a type starting by `EQUIPMENT|C|CA|`)_ when listing equipments in a building group
```json title="Using Regex to exclude types"
"search": {
"type": {
"matches": ["EQUIPMENT\\|.*"],
"notMatches": ["EQUIPMENT\\|C\\|CA\\|.*"]
},
"parentRecursive": {
"externalIds": { "CODE": "SITE_001" }
}
}
```
#### By strict equality
Exclude specified equipments _(those having a type equals to `EQUIPMENT|C|CA|CA01` or `EQUIPMENT|A|AA`)_ when listing equipments in a building group
```json title="Using strict equality to exclude types"
"search": {
"type": {
"matches": ["EQUIPMENT\\|.*"],
"notEquals": ["EQUIPMENT|C|CA|CA01", "EQUIPMENT|A|AA"]
},
"parentRecursive": {
"externalIds": { "CODE": "SITE_001" }
}
}
```
### Filtering on a property's current data
Assets carry **properties**: data points like address, rental area, construction date, or number of tenants. Each property is identified by a **code** (e.g. `ADDRESS`, `RENTAL_AREA`, `BUILDING_DATE`, `TENANT_COUNT`).
:::info
Property codes for each asset type are listed in [the DQC product](https://app.stonal.io/dqc). See [Fetch a subset of properties](#fetch-a-subset-of-properties) for details.
:::
Add a `search.properties` block to filter assets by their property values.
:::tip
`search.properties` **filters** which assets are returned. The root-level `properties` controls **which data** appears in the response. They can be used together.
:::
**Example:** buildings in 8th arrondissement of Paris with more than 2 tenants
```json
{
"search": {
"properties": {
"conditions": [
{ "op": "gt", "property": "TENANT_COUNT", "value": "2" },
{ "op": "contains", "property": "ADDRESS", "value": "75008 Paris" }
]
},
"type": {
"equals": ["BUILDING"]
}
},
"fields": ["uid", "name", "type"],
"properties": {
"simple": ["TENANT_COUNT", "ADDRESS"]
},
"paginate": { "size": 50 }
}
```
#### Condition structure
A condition is an object with the following fields:
| Field | Description |
|-------|-------------|
| `op` | The operator to apply (see [Operators](#operators) below) |
| `property` | The property code to filter on (for leaf conditions) |
| `value` | The value to compare against (for single-value operators) |
| `values` | A list of values to compare against (for `in`/`notIn` operators) |
| `conditions` | A list of nested conditions (for `and`/`or` group operators) |
Multiple conditions at root level are combined with **AND**. Use `or`/`and` groups for explicit logic:
```json title="๐ address contains 75008 Paris OR more than 10 tenants"
{ "op": "or", "conditions": [
{ "op": "contains", "property": "ADDRESS", "value": "75008 Paris" },
{ "op": "gt", "property": "TENANT_COUNT", "value": "10" }
]}
```
Groups can be nested:
```json title="๐ main property type is Bureau AND (rental area > 500 OR more than 10 tenants)"
{
"conditions": [
{ "op": "eq", "property": "MAIN_ASSET_NATURE", "value": "Bureau" },
{ "op": "or", "conditions": [
{ "op": "gt", "property": "RENTAL_AREA", "value": "500" },
{ "op": "gt", "property": "TENANT_COUNT", "value": "10" }
]}
]
}
```
#### Operators
##### Comparison
Text comparisons are case and accent insensitive ("chatelet" matches "Chรขtelet").
| Operator | Description | Required fields |
|----------|-------------|-----------------|
| `eq` | Equals | `value` |
| `neq` | Not equals | `value` |
| `in` | Matches any value in a list | `values` (non-empty) |
| `notIn` | Matches none of the values | `values` (non-empty) |
```json title="๐ main property type is Bureau"
{ "op": "eq", "property": "MAIN_ASSET_NATURE", "value": "Bureau" }
```
```json title="๐ main property type is Commerce or Logement"
{ "op": "in", "property": "MAIN_ASSET_NATURE", "values": ["Commerce", "Logement"] }
```
##### Text
| Operator | Description | Required fields |
|----------|-------------|-----------------|
| `contains` | Substring match (case/accent insensitive) | `value` |
| `notContains` | Excludes substring match | `value` |
| `regex` | Regular expression match | `value` (valid regex) |
| `notRegex` | Excludes regex match | `value` (valid regex) |
```json title="๐ address contains 'Rue du chat'"
{ "op": "contains", "property": "ADDRESS", "value": "Rue du chat" }
```
##### Numeric and date
All values are passed as strings but must be a valid **number** (e.g. `"100"`) or **ISO 8601 date** (e.g. `"2024-01-15"`).
| Operator | Description | Required fields |
|----------|-------------|-----------------|
| `gt` | Greater than | `value` (number or date) |
| `gte` | Greater than or equal | `value` (number or date) |
| `lt` | Less than | `value` (number or date) |
| `lte` | Less than or equal | `value` (number or date) |
```json title="๐ rental area between 100 and 500"
{
"conditions": [
{ "op": "gte", "property": "RENTAL_AREA", "value": "100" },
{ "op": "lte", "property": "RENTAL_AREA", "value": "500" }
]
}
```
```json title="๐ buildings constructed after 2000"
{ "op": "gt", "property": "BUILDING_DATE", "value": "2000-01-01" }
```
##### Presence
| Operator | Description | Required fields |
|----------|-------------|-----------------|
| `isNull` | No current value for this property | none (`value`/`values` must be absent) |
| `isNotNull` | Has a current value | none (`value`/`values` must be absent) |
```json title="๐ assets with no address"
{ "op": "isNull", "property": "ADDRESS" }
```
##### Logic groups
| Operator | Description | Required fields |
|----------|-------------|-----------------|
| `and` | All nested conditions must match | `conditions` (at least 2) |
| `or` | At least one must match | `conditions` (at least 2) |
#### Negation and missing data
Negation operators (`neq`, `notIn`, `notContains`, `notRegex`) exclude assets that have **no value** for the property. To include them, combine with `isNull`:
```json title="๐ main property type is not Commerce or Parking, including assets without one"
{ "op": "or", "conditions": [
{ "op": "notIn", "property": "MAIN_ASSET_NATURE", "values": ["Commerce", "Parking"] },
{ "op": "isNull", "property": "MAIN_ASSET_NATURE" }
]}
```
### Fetch a subset of fields
Fields are attributes directly associated with an asset. You can specify which fields to retrieve.
Allowed values: uid, name, updatedAt, deletedAt, type, externalIds, representations, parent
Unique identifier `uid` is always returned.
```json title="๐ get asset's name, date of update and external ids"
{
"search": {
"asset": { "uid": "2cb3683a-90f6-48de-aaa0-08f45c7bf808" }
},
"fields": ["name", "updatedAt", "externalIds"]
}
```
### Fetch a subset of properties
Properties are associated with time-series data linked to an asset.
You can fetch the reference data for a specified property at the current time.
:::info
Items of `simple` list are referring to propertyโs `code`.
This code can be found in [our DQC product](https://app.stonal.io/dqc) for each asset's type
In this example we are on an BUILDING, its property named *Adresse* has `ADDRESS` for code.
Screenshoot from DQC on a URL like `https://app.stonal.io/dqc/$ORGANIZATION_CODE/asset-types/EQUIPMENT/assets/$ASSET_UID`

Nb: code can also have uuid format
In this example we are on an EQUIPMENT, its property named *Date dernier renouvellement* has `43f1f705-b3b3-4ff3-9ca3-a5c5ed638eef` for code.
Screenshoot from DQC on a URL like `https://app.stonal.io/dqc/$ORGANIZATION_CODE/asset-types/EQUIPMENT/assets/$ASSET_UID`

:::
```json title="๐ get building's data for ADDRESS and BUILDING_DATE property"
{
"search": {
"type": { "equals": ["BUILDING"] }
},
"properties": {
"simple": ["ADDRESS", "BUILDING_DATE"]
}
}
```
```json title="โก๏ธ response with current reference data for ADDRESS and BUILDING_DATE property"
{
"assets": [
{
"uid": "1321987a-b94f-4180-ab93-94432be0855f",
"properties": {
"simple": {
"ADDRESS": "28 Cr Albert 1er, 75008 Paris",
"BUILDING_DATE": "2001-09-29"
}
}
}
]
}
```
### Differential update
You can search data that has been updated since a particular date.
```json title="๐ differential update"
"search": {
"updatedAtAfter": "2025-09-12T00:00:00"
}
```
### Detect deleted assets
In search response, you can also include deleted assets matching specified filters.
```json title="๐ detect deleted assets"
"search": {
"updatedAtAfter": "2025-09-12T00:00:00",
"includeDeleted": true
}
```
## Asset pictures
This mostly applies to `BUILDING_GROUP` and `BUILDING` assets.
If the asset has a `PHOTO_ID`, we can access it directly using this pattern of URL:
```
https://api.stonal.io/document-storage/v1/organizations/$org/documents/$photoId/content
```
We also provide a resizing and thumbnail generation for picture using [imagor](https://github.com/cshum/imagor) so that you can use it direclty in your frontend API:
```
https://cdn-api.stonal.io/imagor/unsafe/fit-in/200x/api.stonal.io/document-storage/v1/organizations/$org/documents/$photoId/content
```
:::info
The `unsafe` in the URL means you don't require to sign the URL but it _is_ safe to use.
:::
---
# Deleting a user
URL: https://docs.stonal.io/md/users/delete-a-user
# Deleting a user
This guide walks through the process of deleting an existing user from the Stonal platform using the API.
:::info Prerequisites
- A valid set of API credentials
- A valid access token (see: [**Get a token**](/md/auth))
- An organization code
:::
### Step 1: Retrieving the user UID
Before deleting a new user, check if they already exist to avoid 404.
See: [**API Specification**](/api/users/get-users-paginated)
```http
GET /v2/organizations/{organizationCode}/users?pageNumber=1&pageSize=10&q=firstName.lastName@corp.com
```
Request body:
```json
{
"email": "firstName.lastName@corp.com"
}
```
If the user exists, you'll receive a 200 response with the user's details including their UID. If not, the user probably doesn't exist.
### Step 2: Delete the user
Once you have confirmed the user's existence and have their ID, you can proceed with deletion.
See: [**API Specification**](/api/users/delete-user)
```http
DELETE /v2/organizations/{organizationCode}/users/{uid}
```
Path parameters:
- `organizationCode`: Your client code (e.g., "STONAL")
- `uid`: UID of user to delete (e.g., "019619df-4767-730f-8d31-143712a08141")
Possible responses:
- 204: User has been successfully deleted
- 404: User to delete does not exist
### Notes
- The deletion operation is permanent and cannot be undone
- The user UID must be obtained from a previous search or stored as an external id on your side
### Error Handling
Common error scenarios:
- User UID not found
- Invalid organization code
- Invalid or expired access token
Always check the response status and handle errors appropriately in your implementation.
---
# Resynchronizing users
URL: https://docs.stonal.io/md/users/legacy/resync-users
# Resynchronizing users
This guide explains how to resynchronize user data in the Stonal platform to ensure consistency between systems.
:::info Prerequisites
- A valid set of API credentials
- A valid access token (see: [**Get a token**](/api/get-access-token))
- An organization code
:::
### Step 1: List existing users
First, retrieve the current list of users to determine which ones need resynchronization.
See: [**API Specification**](/api/legacy/search-user-on-organization)
```http
POST /v1/organizations/{organizationCode}/users/search
```
Request body:
```json
{}
```
The response will include all current users in the system.
### Step 2: Take action for each user
#### Create - The user exists on your system but not in Stonal
See: [**User creation**](/md/users/legacy/create-a-user)
#### Update - The user exists in Stonal and on your system
See: [**User update**](/md/users/legacy/modify-a-user)
#### Delete - The user exists in Stonal but not on your system
See: [**User deletion**](/md/users/legacy/delete-a-user)
---
# Retrieve documentations for an asset
URL: https://docs.stonal.io/md/documents/get-documentations
# Retrieve documentations for an asset
This guide explains how to retrieve documentations tree associated with a specific asset using the public API.
:::info Prerequisites
- A valid set of API credentials
- A valid access token
- A valid organization code
- A valid asset ID
:::
### Step 1: Identify the asset
You must provide **either** the `assetId` which can be either the asset uid or the external id of the asset you want to retrieve documentation for.
The asset uid format is `asset-123` and the external id format is `ERP:BAT001` (a composition of the type code and the asset code separated by a colon).
- If it is not provided, the API returns a `400 Bad Request`.
:::note
Ensure the user has the necessary access rights to the organization and asset to successfully retrieve the documentation.
:::
### Step 2: Call the API
See: [**API Specification**](/api/documents/get-documentations-for-asset)
```http
GET /document-storage/v1/organizations/{organizationCode}/assets/{assetId}/documentations
```
Query Parameters:
Name Type Required Description
assetId string true Unique identifier of the asset or External identifier of the asset
:::info Important
At least one of assetUid or externalId must be provided.
:::
Example request using assetUid:
```bash
curl -X GET "https://api.stonal.io/document-storage/v1/organizations/STONAL/assets/asset-123/documentations" \
-H "Authorization: Bearer {token}"
```
Example request using externalId:
```bash
curl -X GET "https://api.stonal.io/document-storage/v1/organizations/STONAL/assets/ERP:BAT001/documentations" \
-H "Authorization: Bearer {token}"
```
## Possible Responses
- `200`: Successfully retrieved documentation entries
- `400`: Bad request (both assetUid and externalId are missing)
- `404`: No documentation found for the given asset
## Notes
- The API returns the documentation structure up to a default depth level (10 levels deep).
- If the asset has no associated documentation, a 404 Not Found is returned.
- Ensure you handle gracefully the cases where the asset does not yet have documentation.
## Error Handling
Common error scenarios:
- Both assetUid and externalId are null โ `400 Bad Request`
- Invalid or unauthorized asset reference โ `404 Not Found`
- Missing or expired access token โ `401 Unauthorized`
Always verify the response status and handle error responses appropriately in your implementation.
---
# Upload a file (Python)
URL: https://docs.stonal.io/md/documents/upload-a-file-python
This guide shows how to upload a file to the Stonal platform using Python.
---
### Prerequisites
* A valid access token (see [Authentication](/md/auth))
* Your organization code (used in the endpoint path)
* Python environment with the `requests` library installed
---
## Upload example
Here's a simple Python example that uploads a file using streaming to minimize memory usage:
```python title="๐ upload.py"
#!/usr/bin/env python3
import json
import requests
from typing import Dict, Any
def upload_file(
organization_code: str,
access_token: str,
file_path: str,
file_mime_type: str,
manifest_data: Dict[str, Any],
env: str = "prod",
) -> Dict[str, Any]:
"""
Upload a file to Stonal platform using streaming to avoid loading entire file in memory.
Args:
organization_code: Your organization code
access_token: OAuth access token
file_path: Path to the file to upload
manifest_data: Manifest JSON data as Python dictionary
Returns:
Response JSON containing documentUid and duplicateDocumentIds
"""
domain: str = {
"prod": "stonal.io",
"staging": "stonal-staging.io",
"test": "stonal-test.io",
"dev": "stonal-dev.io",
}[env]
url = f"https://api.{domain}/document-storage/v1/organizations/{organization_code}/files/upload"
headers: Dict[str, str] = {"Authorization": f"Bearer {access_token}"}
# Create manifest JSON string
manifest_json: str = json.dumps(manifest_data)
# Open file in binary mode for streaming
with open(file_path, "rb") as file:
files = {
"manifest": ("manifest.json", manifest_json, "application/json"),
"file": (file_path.split("/")[-1], file, file_mime_type),
}
response: requests.Response = requests.post(url, headers=headers, files=files)
response.raise_for_status()
return response.json()
# Example usage
def main():
# Example manifest for asset + documentation strategy
manifest: Dict[str, Any] = {
"asset": {"uid": "ac6d8be3-5036-47be-a010-eb3bbe0d7ae8"},
"documentation": {"uid": "dbce00ca-3540-4d1c-a11c-5474400d2b19"},
}
try:
result: Dict[str, Any] = upload_file(
organization_code="DEMO",
access_token="33286103-9b5b-43c8-9183-89b869f88861",
file_path="document.pdf",
file_mime_type="application/pdf",
manifest_data=manifest,
)
print(f"Upload successful! Document UID: {result['documentId']}")
if result.get("duplicateDocumentIds"):
print(f"Duplicate documents found: {result['duplicateDocumentIds']}")
except requests.exceptions.HTTPError as e:
print(f"Upload failed: {e}")
print(f"Response: {e.response.reason}")
if __name__ == "__main__":
main()
```
---
## Notes
* **Memory efficient**: Uses file streaming to avoid loading the entire file into memory
* **Error handling**: Proper HTTP error handling with detailed responses
---
For complete manifest options, refer to the [Upload a file](/md/documents/upload-a-file) documentation.
---
# Resynchronizing users
URL: https://docs.stonal.io/md/users/resync-users
# Resynchronizing users
This guide explains how to resynchronize user data in the Stonal platform to ensure consistency between systems.
:::info Prerequisites
- A valid set of API credentials
- A valid access token (see: [**Get a token**](/md/auth))
- An organization code
:::
### Step 1: List existing users
First, retrieve the current list of users to determine which ones need resynchronization.
See: [**API Specification**](/api/users/get-users-paginated)
```http
GET /v2/organizations/{organizationCode}/users?pageNumber=1&pageSize=10
```
The response will include all current users in the system.
### Step 2: Take action for each user
#### Create - The user exists on your system but not in Stonal
See: [**User creation**](/md/users/create-a-user)
#### Update - The user exists in Stonal and on your system
See: [**User update**](/md/users/modify-a-user)
#### Delete - The user exists in Stonal but not on your system
See: [**User deletion**](/md/users/delete-a-user)
---
# Retrieve documentation templates
URL: https://docs.stonal.io/md/documents/get-templates
# Retrieve documentation templates
This guide explains how to retrieve documentation templates and their detailed structure using the public API.
:::info Prerequisites
- A valid set of API credentials
- A valid access token
- A valid organization code
- The scope permission `stonal.document.template.read`
:::
## Overview
The documentation templates API provides two main endpoints:
1. **List templates** - Get all available documentation templates for an organization
2. **Get template details** - Retrieve the complete folder tree structure for a specific template
---
## List documentation templates
### Step 1: Call the API
See: [**API Specification**](/api/documents/get-templates)
```http
GET /document-storage/v1/organizations/{organizationCode}/templates
```
### Parameters
| Name | Type | Required | Description |
|------|------|----------|-------------|
| organizationCode | string | true | Organization code (path parameter) |
| language | string | false | Preferred language (query parameter, e.g., "fr-FR") |
### Example request
```bash
curl -X GET "https://api.stonal.io/document-storage/v1/organizations/STONAL/templates?language=fr-FR" \
-H "Authorization: Bearer {token}"
```
### Response
```json
{
"templates": [
{
"id": "f90ee77d-b6cc-41fa-8d1f-bea649e6d93d",
"name": "DOE Template",
"organizationCode": "STONAL",
"attachmentType": "BUILDING_GROUP"
}
]
}
```
### Possible responses
- `200`: Successfully retrieved templates list
- `403`: Forbidden (insufficient permissions)
---
## Get documentation template details
### Step 1: Identify the template
You must provide the `templateId` which is the unique identifier of the template you want to retrieve details for.
The template ID format is typically a UUID: `f90ee77d-b6cc-41fa-8d1f-bea649e6d93d`
:::note
Ensure the user has the necessary access rights to the organization and template to successfully retrieve the template details.
:::
### Step 2: Call the API
See: [**API Specification**](/api/documents/get-template)
```http
GET /document-storage/v1/organizations/{organizationCode}/templates/{templateId}
```
### Parameters
| Name | Type | Required | Description |
|------|------|----------|-------------|
| organizationCode | string | true | Organization code (path parameter) |
| templateId | string | true | Template unique identifier (path parameter) |
| language | string | false | Preferred language (query parameter, e.g., "fr-FR") |
### Example request
```bash
curl -X GET "https://api.stonal.io/document-storage/v1/organizations/STONAL/templates/f90ee77d-b6cc-41fa-8d1f-bea649e6d93d?language=fr-FR" \
-H "Authorization: Bearer {token}"
```
### Response
```json
{
"template": {
"id": "f90ee77d-b6cc-41fa-8d1f-bea649e6d93d",
"name": {
"fr-FR": "Modรจle DOE"
},
"organizationCode": "STONAL",
"attachmentType": "BUILDING_GROUP",
"folders": [
{
"id": "folder-123",
"name": {
"fr-FR": "Documentation technique"
},
"parentId": null,
"documentClass": {
"identifier": "documentClassIdentifier",
"code": "DPE"
}
}
]
}
}
```
### Possible responses
- `200`: Successfully retrieved template details
- `404`: Template not found
- `403`: Forbidden (insufficient permissions)
## Notes
- **Language parameter**: When provided, the API returns localized names in the specified language. If not provided or invalid, defaults to the system default language.
- **Folder hierarchy**: The folders array contains all folders with their parent-child relationships defined by the `parentId` field.
- **Root folders**: Folders with `parentId: null` are root-level folders.
- **Document classes**: Some folders may have associated document classes that define the type of documents that can be stored.
## Error Handling
### Common error scenarios:
- **Missing or expired access token** โ `401 Unauthorized`
- **Insufficient scope permissions** โ `403 Forbidden`
- **Invalid organization code** โ `403 Forbidden`
- **Template not found or no access** โ `404 Not Found`
### Best practices:
- Always verify the response status code before processing the response
- Handle error responses gracefully in your implementation
- Cache template lists when appropriate to reduce API calls
- Use the language parameter to provide localized user experiences
:::tip Performance
The template details endpoint is optimized to avoid construction-then-flattening patterns for better performance when retrieving large folder structures.
:::
## Security
Both endpoints require:
- Valid authentication token
- The `stonal.document.template.read` scope permission
- Appropriate access rights to the organization and templates
The API automatically filters results based on the user's permissions, ensuring users only see templates they have access to.
---
# Create a Contract
URL: https://docs.stonal.io/md/contracts/creation
## Create a Contract โ `POST`
When creating a contract, you can provide:
- A list of asset stonal UIDs
- A list of stakeholder UIDs
- A list of equipment types (holdertype codes)
> This translates to: *"I am creating a contract for this equipment type,
> within these assets, with these contacts as stakeholders."*
```bash
curl --location 'https://api.stonal.io/datalake/v1/organizations/$organization/contracts' \
--header 'Authorization: Bearer $token' \
--header 'Content-Type: application/json' \
--data '{
"assets": ["uid1", "uid2"],
"stakeholders": ["uid1", "uid2"],
"equipments": ["EQUIPMENT|K|KG|KG08|KG0811"],
"effectiveDate": "2025-12-10",
"endDate": "2026-12-10"
}'
```
### Response
A successful creation returns a **HTTP 201** status.
---
# Create a Stakeholder
URL: https://docs.stonal.io/md/stakeholders/creation
## Create a Stakeholder โ `POST`
### Physical person (`type = PERSON`)
**Required fields:** `email` (unique constraint)
```bash
curl --location 'https://api.stonal.io/datalake/v1/organizations/$organization/stakeholders' \
--header 'Authorization: Bearer $token' \
--header 'Content-Type: application/json' \
--data-raw '{
"type": "person",
"firstName": "John",
"lastName": "Doe",
"address": "string",
"postalCode": "string",
"city": "string",
"externalCode": "string",
"contactsDetails": [
{
"email": "john.doe@example.com",
"phoneNumber": "0123456789",
"additionalInfos": "string"
}
],
"isActive": true,
"fax": "string",
"function": "string"
}'
```
### Legal entity (`type = COMPANY`)
**Required fields:** `siren` and `companyName` (unique constraint)
```bash
curl --location 'https://api.stonal.io/datalake/v1/organizations/$organization/stakeholders' \
--header 'Authorization: Bearer $token' \
--header 'Content-Type: application/json' \
--data-raw '{
"type": "company",
"companyName": "Acme Corporation",
"siren": 123456789,
"address": "string",
"postalCode": "string",
"city": "string",
"externalCode": "string",
"contactsDetails": [
{
"email": "john.doe@example.com",
"phoneNumber": "0123456789",
"additionalInfos": "string"
}
],
"isActive": true,
"fax": "string",
"function": "string"
}'
```
### Response
A successful creation returns a **HTTP 201** status with the `uid` of the created stakeholder.
---
# Delete a Stakeholder
URL: https://docs.stonal.io/md/stakeholders/delete
## Delete a Stakeholder โ `DELETE`
```bash
curl --location --request DELETE 'https://api.stonal.io/datalake/v1/organizations/$organization/stakeholders/f2f6dbe5-8bb6-46a9-9684-41ca61c8b9db' \
--header 'Authorization: Bearer $token'
```
---
# Managing assets
URL: https://docs.stonal.io/md/assets
# Managing assets
## Goals
The core goals of these APIs are to make it simple to:
- Import, synchronize, and link assets from and to your IT systems
- Link assets to different third party systems
- Create links between assets
- Store data around assets from different sources
- Search for assets
They do not expose all the capabilities of the underlying asset management systems, but they are designed to be simple to use and to be extended.
## Core data model
### Assets and properties
The core data model is the following:
- **Asset**: A physical or logical description of a real-estate world object
- **Property**: A characteristic of an asset
- **Data**: Some property values around an asset
### Relation between assets
To get a better understanding of how the asset hierarchy works at stonal, you
should read [this page](hierarchy/index.md).
---
# Managing Contracts
URL: https://docs.stonal.io/md/contracts
# Managing Contracts
This documentation will help you use the public Contracts API:
creation, retrieval, and update.
## Concept
A **contract** can be linked to:
- One or more **assets**
- One or more **equipments** (identified by holdertype codes)
- One or more **stakeholders**
Contracts can represent various use cases, such as rental leases or equipment
maintenance contracts within assets.
| Parameter | Format |
|-----------|--------|
| `assets` | `["uid1", "uid2", ...]` |
| `stakeholders` | `["uid1", "uid2", ...]` |
| `equipments` | `["holdertype_code1", "holdertype_code2", ...]` |
---
## Authentication
All requests require a Bearer token and an organization code:
```bash
organization=your_organization
token=your_token
```
---
# Managing Stakeholders
URL: https://docs.stonal.io/md/stakeholders
# Managing Stakeholders
This documentation will help you use the public Stakeholders API:
creation, retrieval, update, and deletion.
## Concept
A **stakeholder** represents a contact linked to contracts in Stonal.
A stakeholder has several properties, the most important being the **type**:
| Type | Description |
|------|-------------|
| `PERSON` | A physical person (individual) |
| `COMPANY` | A legal entity (company) |
A stakeholder can be linked to multiple contracts.
---
## Authentication
All requests require a Bearer token and an organization code:
```bash
organization=your_organization
token=your_token
```
---
# Managing users
URL: https://docs.stonal.io/md/users
# Managing users
This document explains the core concepts of user management in the Stonal platform. Understanding these concepts is crucial for effectively managing user access and permissions.
## Concepts
### Groups
Groups are used to group users together and assign them permissions in bulk. Smart doc also uses groups to control access to documents.
:::info
They are assigned to users via the `groupUids` field.
:::
### Profiles
Profiles represent a functional scope within the system. They are primarily used for informational purposes and provide a high-level categorization of users.
Some applications will use profiles to control access rights, like Keep and Model for example.
:::info
They are assigned to users via the `permissions.uid` field.
:::
### Application Groups
Authorization models are the primary mechanism for defining the set of applications that a user can access.
:::info
They are assigned to users via the `permissions.uid` field.
:::
:::note Regarding document storage
The document storage application access is granted via the `stonal.application.platform` application.
If a given application group contains the `stonal.application.platform` application and document storage is enabled on your organization, users assigned to this application group will be able to access the document storage application.
:::
### Assets
Permissions provide fine-grained control over what specific resources and actions a user can access within the system.
List of currently available permission types:
- `ASSET`
List of currently available permission `ASSET` subtypes:
- `PORTFOLIO`
- `COMPANY`
- `FACILITY`
- `BUILDING_GROUP`
- `BUILDING`
:::info
They are assigned to users via the `permissions.uid` field.
:::
## Authorization schema
```mermaid
graph TD;
User-->Permissions
Permissions-->Profiles
Permissions-->Assets
Permissions-->Companies
Permissions-->Portfolios
Permissions-->ReportGroups
Companies-->Assets
Portfolios-->Assets
ReportGroups-->Reports
User-->Groups
Groups-->Permissions
Permissions-->ScopeGroup[Scope groups]
ScopeGroup-->Right
ScopeGroup-->Application
```
## Guides
```mdx-code-block
```
---
# Managing users
URL: https://docs.stonal.io/md/users/legacy
# Managing users
This document explains the core concepts of user management in the Stonal platform. Understanding these concepts is crucial for effectively managing user access and permissions.
## Concepts
### Companies
Companies in Stonal represent organizational units that users belong to and play a role in asset access control.
A given company grants the user access to every asset it owns.
:::info
They are assigned to users via the `company` field.
:::
### Profiles
Profiles represent a functional scope within the system. They are primarily used for informational purposes and provide a high-level categorization of users.
Some applications will use profiles to control access rights, like Keep and Model for example.
:::info
They are assigned to users via the `authorization.profile` field.
:::
### Authorization Models (Application Groups)
Authorization models are the primary mechanism for defining the set of applications that a user can access.
:::info
They are assigned to users via the `authorization.modelId` field.
:::
:::note Regarding document storage
The document storage application access is granted via the `PLATFORM` application.
If a given application group contains the `PLATFORM` application and document storage is enabled on your organization, users assigned to this application group will be able to access the document storage application.
:::
### Permissions
Permissions provide fine-grained control over what specific resources and actions a user can access within the system.
List of currently available permission types:
- `ASSET`
List of currently available permission `ASSET` subtypes:
- `PORTFOLIO`
- `FACILITY`
- `BUILDING_GROUP`
- `BUILDING`
:::info
They are assigned to users via the `permissionUids` field.
:::
## Authorization schema
```mermaid
graph TD;
User-->Profile;
Profile-->keepModel[Keep/Model rights]
User-->Company;
Company-->asset[Assets access];
User-->AuthorizationModel;
AuthorizationModel-->app[Application access];
User-->Permissions;
Permissions-->asset[Asset access];
```
## Guides
```mdx-code-block
```
---
# Receiving live updates
URL: https://docs.stonal.io/md/webhooks
# Receiving live updates
## How webhooks work
- Webhooks are a way to notify you when certain events happen in the Stonal platform.
- It works by calling an HTTP endpoint of your choice on your end when an event occurs.
- They are currently only available for events related to **documents**.
- They cover all changes to documents, including creation, update and deletion, and are
triggered for manual changes as well as automated ones (e.g. OCR, AI classification, etc).
## How to use webhooks
1. You register a webhook endpoint
2. When a document changes in your organization, Stonal sends an HTTP `POST` request with a JSON payload
to your endpoint
3. Your endpoint processes the notification and responds with a `200 OK` status code
## Registering a webhook
Webhooks are registered per organization by contacting the Stonal support team.
:::info We will do better
We will allow to register webhooks directly from the platform in the future.
:::
## Events
The only events currently supported are around the documents lifecycle: creation, update and deletion of documents.
### Document Created / Updated
This event is triggered when a document is created or updated. The two
operations have exactly the same `document` payload.
#### Payload
```json
{
"organizationCode": "SAMPLE_ORG",
"type": "document_change",
"operation": "create",
"document": {
"id": "efb614e6-f1b9-4829-acc4-2c8e26ab909a",
"name": "test.pdf",
"type": "application/pdf",
"metadata": {
"KEY_1": "VALUE_1",
"KEY_2": "VALUE_2"
}
}
}
```
#### Field Description
| Field | Description |
|-------|-------------|
| `organizationCode` | The unique identifier for your organization in Stonal |
| `type` | The type of event, in this case `document_change` |
| `operation` | The operation performed, in this case `create` or `update` |
| `document.id` | The unique identifier of the created document |
| `document.name` | The filename of the document |
| `document.type` | The MIME type of the document |
| `document.metadata` | Custom metadata key-value pairs associated with the document |
### Document Deleted
This event is triggered when a document is deleted.
#### Payload
```json
{
"organizationCode": "SAMPLE_ORG",
"type": "document_change",
"operation": "delete",
"document": {
"id": "efb614e6-f1b9-4829-acc4-2c8e26ab909a"
}
}
```
## Server implementation
Your endpoint should respond with a `200 OK` status code to the webhook request. If it does not,
the webhook will be retried up to 3 times.
## Notes
### Number of queued payloads
There is no limit on the number of queued payloads that can be transmitted to you. They are
currently sent sequentially per organization, but this might change in the future.
---
# Search Contracts
URL: https://docs.stonal.io/md/contracts/search
## Search Contracts โ `GET`
Contracts are searched using **asset UIDs** and/or **stakeholder UIDs** and/or **contract code**.
> **Important:** You must provide the **exhaustive list** of assets and
> stakeholders to retrieve the correct contract.
```bash
curl --location 'https://api.stonal.io/datalake/v1/organizations/$organization/contracts?assetUids=eef91258-3c70-49c8-8a67-586e5cb7eeee%2Caaf91258-3c70-49c8-8a67-586e5cb7eeee&stakeholderUids=eef91258-3c70-49c8-8a67-586e5cb7ee02&contractCode=test/2026' \
--header 'Authorization: Bearer $token'
```
Returns a list of contracts matching the provided criteria.
---
# Search Stakeholders
URL: https://docs.stonal.io/md/stakeholders/search
## Search Stakeholders โ `GET`
Two search modes are available:
### Full-text search
Uses the `q` parameter. **Cannot be combined with other parameters** only mustHave, include, size, direction and sort can be used.
```bash
curl --location 'https://api.stonal.io/datalake/v1/organizations/$organization/stakeholders?size=10&q=123231124&direction=asc&sort=name&include=CONTRACTS&mustHave=SIREN' \
--header 'Authorization: Bearer $token'
```
### Targeted search
Uses specific parameters. **Can be combined.**
```bash
curl --location 'https://api.stonal.io/datalake/v1/organizations/$organization/stakeholders?name=john' \
--header 'Authorization: Bearer $token'
```
Returns a list of stakeholders matching the search criteria.
---
# Server implementation
URL: https://docs.stonal.io/md/webhooks/server
# Server implementation
These are sample server implementations of webhook endpoints in different programming languages.
:::info
We will gladly provide you with sample code in other languages/frameworks if you need it.
:::
## Node.js Example
```javascript
const express = require('express');
const app = express();
app.use(express.json());
app.post('/webhook', (req, res) => {
const event = req.body;
console.log('Received webhook event:', JSON.stringify(event, null, 2));
// Validate the event
if (!event.organizationCode || !event.type || !event.operation || !event.doc) {
return res.status(400).send('Invalid event payload');
}
// Process based on operation type
switch(event.operation) {
case 'create':
handleDocumentCreated(event.doc);
break;
case 'update':
handleDocumentUpdated(event.doc);
break;
case 'delete':
handleDocumentDeleted(event.doc);
break;
default:
console.log('Unknown operation:', event.operation);
}
// Return a 200 OK to acknowledge receipt
res.status(200).send('Event received');
});
function handleDocumentCreated(doc) {
console.log(`Document created: ${doc.id} - ${doc.name}`);
// Your business logic here
}
function handleDocumentUpdated(doc) {
console.log(`Document updated: ${doc.id} - ${doc.name}`);
// Your business logic here
}
function handleDocumentDeleted(doc) {
console.log(`Document deleted: ${doc.id} - ${doc.name}`);
// Your business logic here
}
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Webhook server listening on port ${PORT}`);
});
```
## Python Example
```python
from flask import Flask, request, jsonify
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
event = request.json
print(f"Received webhook event: {event}")
# Validate the event
if not all(k in event for k in ['organizationCode', 'type', 'operation', 'doc']):
return jsonify({'error': 'Invalid event payload'}), 400
# Process based on operation type
if event['operation'] == 'create':
handle_document_created(event['doc'])
elif event['operation'] == 'update':
handle_document_updated(event['doc'])
elif event['operation'] == 'delete':
handle_document_deleted(event['doc'])
else:
print(f"Unknown operation: {event['operation']}")
# Return a 200 OK to acknowledge receipt
return jsonify({'status': 'success'}), 200
def handle_document_created(doc):
print(f"Document created: {doc['id']} - {doc['name']}")
# Your business logic here
def handle_document_updated(doc):
print(f"Document updated: {doc['id']} - {doc['name']}")
# Your business logic here
def handle_document_deleted(doc):
print(f"Document deleted: {doc['id']} - {doc['name']}")
# Your business logic here
if __name__ == '__main__':
app.run(host='0.0.0.0', port=3000)
```
## Best Practices
1. **Always respond quickly**: Webhooks expect a fast response (within a few seconds). For long-running processes, acknowledge the webhook immediately and process the event asynchronously.
2. **Implement retry logic**: Stonal may retry failed webhook deliveries. Implement idempotency in your handlers to avoid duplicate processing.
3. **Secure your endpoint**: Consider using HTTPS.
4. **Set up monitoring**: Monitor your webhook endpoint for availability and response times.
5. **Handle errors gracefully**: Log errors for debugging, but ensure your webhook endpoint still returns a `200 OK` response when you've successfully received the event, even if your processing has errors.
---
# Testing webhooks
URL: https://docs.stonal.io/md/webhooks/sample
# Testing webhooks
## Sending a standalone document
The Stonal plateform is centered arround assets. For the smartdoc product, we also require a documentation.
We recently started to offer a "disconnected" mode where you can skip these requirements to simply send documents,
retrieve some core information around them and do the linking to assets and documentations later.
This is how you can do it:
1. You declare a `STONAL_TOKEN` environment variable fetched from the [Personal Access Tokens management page](/#using-your-personal-access-token).
```shell title="Declaring a token"
export STONAL_TOKEN=""
```
2. You create a manifest file declare the file you want to send as "disconnected":
```shell title="Creating a manifest"
echo '{"disconnected": true}' > manifest.json
```
3. You send the file to the `/upload` endpoint:
```shell title="Sending the file"
curl https://api.stonal-dev.io/document-storage/public/organizations/DEMO/files/upload \
-H "Authorization: Bearer $STONAL_PAT" \
-F 'manifest=@manifest.json;type=application/json' \
-F 'file=@sample.pdf'
```
4. You get a response similar to this:
```json title="Server response"
{"documentId":"efb614e6-f1b9-4829-acc4-2c8e26ab909a","duplicateDocumentIds":["434f3154-3c55-4c6f-a759-6de762251ab0"]}
```
If you have some values within the `duplicateDocumentIds` array, it means that another document with the same content
already exists within the organization. This is not of any concern, you can safely ignore this.
---
# Update a Contract (partial)
URL: https://docs.stonal.io/md/contracts/update
## Partial Update โ `PATCH`
Partially updates a contract.
> **Important:** This API works with lists. The update replaces the entire
> field value:
> - To **add** an item: send the current list **plus** the new item
> - To **remove** an item: send the current list **minus** the targeted item
>
> Pay special attention to the fields: `assetUids`, `stakeholderUids` and `equipments`.
```bash
curl --location --globoff --request PATCH 'https://api.stonal.io/datalake/v1/organizations/$organization/contracts' \
--header 'Authorization: Bearer $token' \
--header 'Content-Type: application/json' \
--data '{
"contractCode": "TEST/2026",
"equipments": ["EQUIPMENT|K|KG|KG08|KG0811"]
}'
```
---
# Update a Stakeholder (full resource)
URL: https://docs.stonal.io/md/stakeholders/updatePut
## Update a Stakeholder โ `PUT`
Replaces the entire stakeholder resource.
```bash
curl --location --request PUT 'https://api.stonal.io/datalake/v1/organizations/$organization/stakeholders/123e4567-e89b-12d3-a456-426614174000' \
--header 'Authorization: Bearer $token' \
--header 'Content-Type: application/json' \
--data-raw '{
"uid": "123e4567-e89b-12d3-a456-426614174000",
"type": "person",
"firstName": "John",
"lastName": "Doe",
"address": "string",
"postalCode": "string",
"city": "string",
"externalCode": "string",
"contactsDetails": [
{
"email": "john.doe@example.com",
"phoneNumber": "0123456789",
"additionalInfos": "string"
}
],
"isActive": true,
"fax": "string",
"function": "string"
}'
```
---
# Update a Stakeholder (partial)
URL: https://docs.stonal.io/md/stakeholders/updatePatch
## Partial Update โ `PATCH`
Partially updates a stakeholder.
> **Important:** When updating a list (e.g. `contactsDetails`), you must send
> the **full updated list** including existing items.
> This is a replace operation on the field, not an append.
In the example below, 3 existing contacts are preserved and 1 new contact is added:
```bash
curl --location --request PATCH 'https://api.stonal.io/datalake/v1/organizations/$organization/stakeholders/f2f6dbe5-8bb6-46a9-9684-41ca61c8b9db' \
--header 'Authorization: Bearer $token' \
--header 'Content-Type: application/json' \
--data-raw '{
"contactsDetails": [
{
"email": "laurent@stonal.com",
"creationDate": "2025-11-26"
},
{
"email": "lolo@stonal.com",
"creationDate": "2025-11-26"
},
{
"email": "lo@stonal.com",
"creationDate": "2025-11-27"
},
{
"email": "zae@stonal.com"
}
]
}'
```
---
# Document Storage API - Public Endpoints
URL: https://docs.stonal.io/api/documents/document-storage-api-public-endpoints
Public API endpoints for document storage service
---
# Download a file
URL: https://docs.stonal.io/api/documents/download-file
Downloads a file by document identifier for a specific organization. The file will be returned as a binary stream.
---
# Get documentations for an asset
URL: https://docs.stonal.io/api/documents/get-documentations-for-asset
Retrieves all documentation entries associated with a specific asset identified by either assetUid or externalId
---
# Get documentation template tree
URL: https://docs.stonal.io/api/documents/get-template
Returns the complete folder tree for a documentation template.
---
# List documentation templates
URL: https://docs.stonal.io/api/documents/get-templates
Returns the documentation templates available for an organization. A language parameter lets clients select the preferred locale for the name field.
---
# Rename a document
URL: https://docs.stonal.io/api/documents/rename-document
Changes the name of a document. This endpoint is available for public use.
---
# Search for documents
URL: https://docs.stonal.io/api/documents/search
Searches for documents based on provided filters with pagination and sorting options. This endpoint is available for public use.
---
# showFileContentDefault
URL: https://docs.stonal.io/api/documents/show-file-content-default
showFileContentDefault
---
# Show file content
URL: https://docs.stonal.io/api/documents/show-file-content
Show a file by its identifier. The file is identified by the document identifier.
---
# Add or remove documentation tags on documents
URL: https://docs.stonal.io/api/documents/update-document-documentation-tags
Updates documentation tags on documents. Each item carries its asset scope in the request body, missing tags are created before assignment and the whole request is processed in one transaction.
---
# Upload a file
URL: https://docs.stonal.io/api/documents/upload-file
Uploads a file with a JSON manifest. The manifest provides context for the upload, such as asset, documentation, linked assets, tags, properties, and destination folder.
---
# Users Service API
URL: https://docs.stonal.io/api/users/users-service-api
Public API endpoints for the users service
---
# Create a group
URL: https://docs.stonal.io/api/users/create-group
> [!important]
---
# Create or update permission
URL: https://docs.stonal.io/api/users/create-permission
> [!important]
---
# Create user
URL: https://docs.stonal.io/api/users/create-user
> [!important]
---
# Delete a group
URL: https://docs.stonal.io/api/users/delete-group
> [!important]
---
# Delete a permission
URL: https://docs.stonal.io/api/users/delete-permission
> [!important]
---
# Delete user
URL: https://docs.stonal.io/api/users/delete-user
> [!important]
---
# Get groups
URL: https://docs.stonal.io/api/users/get-groups
> [!important]
---
# Get permissions
URL: https://docs.stonal.io/api/users/get-permissions
> [!important]
---
# Get user by UID
URL: https://docs.stonal.io/api/users/get-user-by-uid
> [!important]
---
# Get paginated users
URL: https://docs.stonal.io/api/users/get-users-paginated
> [!important]
---
# Update a group
URL: https://docs.stonal.io/api/users/update-group
> [!important]
---
# Update user
URL: https://docs.stonal.io/api/users/update-user
> [!important]
---
# Datalake API - Public Endpoints
URL: https://docs.stonal.io/api/assets/datalake-api-public-endpoints
Public API endpoints for the datalake service
---
# Create contracts
URL: https://docs.stonal.io/api/assets/create-contract
Create new contracts for an organization with his stakeholders and assets linked. Requires `stonal.asset.write` permission
---
# Create a selection session
URL: https://docs.stonal.io/api/assets/create-selection-session
Creates a new selection session with selection data and context for the specified organization. Requires `stonal.asset.read` permission
---
# Create stakeholder
URL: https://docs.stonal.io/api/assets/create-stakeholder
Creates a new stakeholder (person or company) for an organization. Requires `stonal.contact.write` permission
---
# Delete a contract
URL: https://docs.stonal.io/api/assets/delete-contract
Delete a contract for an organization. Requires `stonal.asset.delete` permission. If the contract code contains URL-unsafe characters (e.g. `/` in `2021/Z5059`), it MUST be **double percent-encoded** in the path โ `2021/Z5059` is sent as `2021%252FZ5059`.
---
# Delete a selection session
URL: https://docs.stonal.io/api/assets/delete-selection-session
Delete a selection session and all its data. Requires `stonal.asset.read` permission
---
# Delete stakeholder
URL: https://docs.stonal.io/api/assets/delete-stakeholder
Deletes a stakeholder by its unique identifier. Requires `stonal.contact.delete` permission
---
# Get all asset types
URL: https://docs.stonal.io/api/assets/get-all-asset-types
Get all asset types by organization
---
# Get all asset properties
URL: https://docs.stonal.io/api/assets/get-all-properties
Get all asset properties by organization
---
# Get contracts
URL: https://docs.stonal.io/api/assets/get-contracts
Filter contracts by asset and/or stakeholder. Requires `stonal.asset.read` permission
---
# Get selected asset details for a selection session
URL: https://docs.stonal.io/api/assets/get-selection-session-assets
Returns the name and level of each selected asset in the session.
---
# Get the plan of the assets base view in selection session
URL: https://docs.stonal.io/api/assets/get-selection-session-plan
Get the plan of the assets base view in selection session.
---
# Get a structure in function of the selection session
URL: https://docs.stonal.io/api/assets/get-selection-session-structure
Get a structure of the unique selection session.
---
# Get a selection session
URL: https://docs.stonal.io/api/assets/get-selection-session
Get a unique selection session with its selection data and context.
---
# Get stakeholder by ID
URL: https://docs.stonal.io/api/assets/get-stakeholder-by-uid
Retrieves a single stakeholder by its unique identifier. Requires `stonal.contacts.read` permission
---
# Update a contract
URL: https://docs.stonal.io/api/assets/patch-contract
Update a contracts for an organization with his stakeholders and assets linked. Requires `stonal.asset.write` permission
---
# Save stakeholder
URL: https://docs.stonal.io/api/assets/save-stakeholder
Creates or updates a stakeholder with a specific UID. Requires `stonal.contact.write` permission
---
# Search assets
URL: https://docs.stonal.io/api/assets/search-assets
Search assets by organization. Requires `stonal.asset.read` permission
---
# Search stakeholders
URL: https://docs.stonal.io/api/assets/search-stakeholder
Search for stakeholders with various filtering options and pagination. Requires `stonal.contacts.read` permission
---
# Update stakeholder
URL: https://docs.stonal.io/api/assets/update-stakeholder
Partially updates an existing stakeholder. Requires `stonal.contact.write` permission
---
# Ingest assets
URL: https://docs.stonal.io/api/assets/upsert-assets
Create and update assets by organization. Requires `stonal.asset.write` permission
---
# API Consumption
URL: https://docs.stonal.io/api/legacy/api-consumption
Welcome to Stonalยฎ API. We provide real estate data for housing and tertiary business. The document below covers how to use our API. Let us know if you any questions.
---
# 1. Get token for Swagger
URL: https://docs.stonal.io/api/legacy/1-get-token-for-swagger
1. Get token for Swagger
---
# 2. API Version 1
URL: https://docs.stonal.io/api/legacy/2-api-version-1
2. API Version 1
---
# 6. API Version 5
URL: https://docs.stonal.io/api/legacy/6-api-version-5
6. API Version 5
---
# Create a new user
URL: https://docs.stonal.io/api/legacy/create
Create a new user.
---
# This is a soft delete of a document, the document will be marked as deleted but not removed from the storage.
URL: https://docs.stonal.io/api/legacy/delete-document
This is a soft delete of a document, the document will be marked as deleted but not removed from the storage.
---
# Delete an existing user
URL: https://docs.stonal.io/api/legacy/delete
Delete an existing user.
---
# Get a file from a document
URL: https://docs.stonal.io/api/legacy/download-file
Get a file from a document
---
# List all authorizations available for users
URL: https://docs.stonal.io/api/legacy/find-all-authorizations-by
List all authorizations available for users
---
# List all companies available for users
URL: https://docs.stonal.io/api/legacy/find-all-companies-by
List all companies available for users
---
# List all groups available for users
URL: https://docs.stonal.io/api/legacy/find-all-groups-by
List all groups available for users
---
# List all permissions available for users
URL: https://docs.stonal.io/api/legacy/find-all-permissions-by
List all permissions available for users
---
# List all profiles available for users
URL: https://docs.stonal.io/api/legacy/find-all-profiles-by
List all profiles available for users
---
# Get a user by its id
URL: https://docs.stonal.io/api/legacy/find-by-organization-code-and-id
Get a user by its id
---
# Search for documents
URL: https://docs.stonal.io/api/legacy/find-by
Search for documents
---
# Get an access token to use for Swagger UI
URL: https://docs.stonal.io/api/legacy/get-access-token
Get an access token to use for Swagger UI
---
# Search users
URL: https://docs.stonal.io/api/legacy/search-user-on-organization
Search users by various filters.
---
# Update an existing user
URL: https://docs.stonal.io/api/legacy/update
Update an existing user.
---
# Upload documents
URL: https://docs.stonal.io/api/legacy/upload-file
Upload documents
---
# Validate documents classification
URL: https://docs.stonal.io/api/legacy/validate-documents-classification
Validate documents classification
---
# Change Events Webhooks format
URL: https://docs.stonal.io/api/webhooks/change-events-webhooks-format
Webhooks format for change events in the stonal stack
---
# Receive document change events
URL: https://docs.stonal.io/api/webhooks/receive-document-change-event
Endpoint for receiving document change events from the stonal platform