# 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: ![Simplified asset hierarchy](./asset-types.png) ## 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` ![dqc_building_properties.png](dqc_building_properties.png)
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` ![dqc_equipment_properties.png](dqc_equipment_properties.png)
::: ```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 ![Create an api user](./resources/create-user.png) ## 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. ::: ![Create a PAT](./resources/create-pat.png) 3. After generation, copy and securely store your token using the provided button ![Save the PAT](./resources/save-pat.png) ## 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 ![Create an API user](./resources/create-user.png) ## 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` ![dqc_building_properties.png](dqc_building_properties.png)
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` ![dqc_equipment_properties.png](dqc_equipment_properties.png)
::: ```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