openapi: 3.0.3
info:
  version: 2.0.0
  title: Cintoo Open API
  description: |-
    Cintoo Open API 1.x documentation: https://aec.cintoo.com/api

    <span class='tag beta'>BETA</span>: API v2 endpoints tagged "BETA" are available in beta-version, meaning that changes may be done later on.

    # API Specification

    The Cintoo API specification document you are viewing is compliant with the Open API Specification 3.0.3 and can be
    exported to json or yaml format that can be used by the API tool of your liking.

    It also means the generated json/yaml will contain all the details present in this documentation about the

    * JSON Objects schema being returned
    * The status codes
    * The authentication
    * The verbs/paths/resources

    ## Technical details

    * The API is **resource oriented**
    * The encoding is done in **utf-8**
    * Do not hesitate to click on the Download button on the top of this page to get the `openapi.json` or `openapi.yaml`
      file generated and import in your favorite API client
    * Any Cintoo valid user can use the API

    - - -

    ## Sandbox

    With Cintoo Cloud API comes a Sandbox, which means a testing environment. This Sandbox is a separate tenant from the
    Cintoo Cloud platform that you are already familiar with.

    The Sandbox is at your disposal so that you can test and use the API with no consequence on your production data (no
    unfortunate deletion by mistake for example).

    ### Sandbox restrictions

    * Scan capacity of 20 scans per user.
    * Company SSO (if any) is not applicable since the Sandbox uses a different account.
    * Please contact <support@cintoo.com> to get the invitation link to the Sandbox account.
    * You can use the same email but you will be asked to set a new password by clicking on the invitation link.
    * Please note that this is an experimental platform, so we reserve the right to refresh/delete the data (unlike the
      production platform).
    * There is no limit in time for using the Sandbox.
    * The BIM & CAD Module (BCM) is activated in this Sandbox, so feel free to use your BIM or CAD model in this testing
      environment.

    Registering to the Sandbox / Cintoo Cloud API

    Please contact our Support Team <support@cintoo.com> to be invited to the Sandbox and get all the documentation for
    Cintoo Cloud API.

    - - -

    ## Authentication

    The Cintoo API relies on JWT Tokens for authenticating users with the standard combination `Access Token`+
    `Refresh Token`.

    Note that the first acquisition of the token pair requires a manual step (see below) for security reasons, then it can
    be fully automated.

    ### Access Tokens and Refresh Tokens

    There are 2 kind of tokens:

    * Access token:
        * is short lived (3h)
        * is used to authenticate each query
        * is personal and should not be shared, except for service users (see `Token management recipes` at the end of the
          documentation)
        * is a JWT and is stateless, ie. there is *NO* limit of Access Token being valid for a given user simultaneously,
          and ***can*** be used in parallel.
        * example of API call to list `accounts`:
          `curl -H Authorization="Bearer $access_token" https://aec.cintoo.com/api/accounts`

    > ***Note***: Calling the API with an already-used/expired token results in a response with status code `401`

    * Refresh token:
        * is long lived (months)
        * is used **once** to obtain a new `Access Token` + `Refresh Token` pair.
        * **cannot** be used to make API calls.
        * is personal and should not be shared, except for service users (see `Token management recipes` at the end of the
          documentation)
        * there ***IS*** a limit of 10 Refresh Token being valid for a given user simultaneously
        * is stateful and ***CANNOT*** be used in parallel, as it is consumed when used
        * example of refresh token call to get a new pair of "Access Token" and "Refresh Token":
          `curl -X POST https://aec.cintoo.com/oauth/token -d "grant_type=refresh_token&refresh_token=$refresh_token"`

    > ***Note***: Calling the API with an already-used/expired token results in a response with status code `401`

    ### The token flow

    The Cintoo API follows the OAuth 2.0 Authorization Grant Type flow, and supports the PKCE extension which is recommended
    to provide better security (source: <https://oauth.net/2/grant-types/authorization-code/>)

    Concretely, users are supposed to acquire the token once with a manual step (they have to click on approve), to get a
    first pair of tokens.

    Once this is done, they can script the refresh with
    a [simple curl command](#refreshing-tokens-ie-generating-programmatically-a-new-access-token--refresh-token-pair).

    > ***Note:*** a refresh token is valid for 21 days. It can be used only once. The refreshing mechanism can be done **an
    unlimited number of times**

    ### Generating Tokens

    * You are just starting with the Cintoo API and want to test it manually -> look that "Generating the JWT Bearer Token
      with the cintoo-login.exe"
    * You are already familiar with oauth mechanisms/tools -> look at "Using tools/3rd party to manage Tokens from Cintoo
      Oauth Service"

    ### Generating the Access Token & Refresh Token pair with the cintoo-login.exe

    To generate an Access Token with OAuth please use the following nice CLI
    tool [cintoo-login.exe](https://cintooprodstatic.blob.core.windows.net/api/cintoo-login-1.1.0-signed.exe) and double
    click on it.

    **If you are on a dedicated tenant, please add the argument `--url`.**

    ```powershell
    .\cintoo-login-1.1.0-signed.exe --url https://aec.cintoo.com

    Opening OAuth page in browser.
    To receive your JWT token:
            * Ensure you are logged-in
            * Click on the 'Allow' button
    INFO: You have 30s to complete these actions
    ```

    Which opens this page

    > Hint: if you wait more than 30s you will have to run the command again
    > Hint: you can change the timeout duration to another value with `--timeout 60`

    ![allow-button](./assets/oauth.png#width=500px;height=350px;)

    Once you click on `Allow` the page should lead you to:

    ![success](./assets/success.png#width=500px;height=350px;)

    *AND* (most importantly) the **token** appears magically in your cmd output

    > Hint: Copy paste the `Bearer xxxxx` to avoid errors

    ```powershell
    Opening OAuth page in browser.
    To receive your JWT token:
            * Ensure you are logged-in
            * Click on the 'Allow' button
    INFO: You have 30s to complete these actions
    Here is your token: to use it put in the Authorization header (copy paste below):
    {"Authorization": "Bearer <access_token>"}
    Its validity is of 3h
    A new token can be-regenerated simply thanks to this refresh_token: <refresh_token>
    Validating token
    Success: API Token was successfully validated
    ```

    ### Refreshing tokens ie. Generating programmatically a new Access Token + Refresh Token pair

    While the Refresh Token is valid (within 21 days), you can consume it to get a new Access-Token + Refresh-Token pair.

    > ***Note:*** consuming the Refresh Token can be done before the expiration of the Access Token
    > ***Note:*** consuming the Refresh Token does **NOT** invalidate other **Access Token** as they are short lived

    Using shell (Linux, OSX)

    ```shell
    refresh_token="<Your refresh token>"
    curl -X POST https://aec.cintoo.com/oauth/token -d "grant_type=refresh_token&refresh_token=$refresh_token"
    ```

    Which returns the following

    ```json
    {
      "access_token": "{access_token}",
      "refresh_token": "{refresh_token}",
      "token_type": "Bearer",
      "expires_in": 10800,
      "user_id": "{user_id}"
    }
    ```

    Using powershell (Windows)

    ```powershell
    $refresh_token = "<Your refresh token>"
    $params = @{
        Uri         = 'https://aec.cintoo.com/oauth/token'
        Method      = 'POST'
        Body        = @{
            grant_type     = 'refresh_token'
            refresh_token  = $refresh_token
        }
        ContentType = 'application/x-www-form-urlencoded'
    }
    Invoke-RestMethod @params
    ```

    Which returns the following

    ```powershell
    access_token  : {access_token}
    refresh_token : {refresh_token}
    token_type    : Bearer
    expires_in    : 10800
    user_id       : {user_id}
    ```

    ### Using tools/3rd party to manage Tokens from Cintoo Oauth Service

    The Cintoo API follows the oauth
    protocol [Authorization Code Grant](https://oauth.net/2/grant-types/authorization-code/) to generate the `access-tokens`
    and `refresh-tokens`.

    As such, it is possible to use 3rd party libraries and tools that implement the Oauth2 Authorization Code protocol to
    manage the tokens (and perform automatic refresh).

    The important fields are the following and they match those defined by
    the [authorization code request](https://www.oauth.com/oauth2-servers/access-tokens/authorization-code-request/), [authorization code request with PKCE](https://www.oauth.com/oauth2-servers/pkce/authorization-request/)
    and [refreshing access tokens](https://www.oauth.com/oauth2-servers/access-tokens/refreshing-access-tokens/) :

    | Field                                  | Value                                                                                                                                                                          |
    |----------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
    | Grant Type                             | `Authorization Code` or `Authorization Code (With PKCE)`                                                                                                                       |
    | Callback URL                           | `localhost` or `127.0.0.1` or `/`                                                                                                                                              |
    | Auth URL                               | `https://aec.cintoo.com/oauth/authorize`                                                                                                                                     |
    | Access Token URL                       | `https://aec.cintoo.com/oauth/token`                                                                                                                                         |
    | Refresh Token URL                      | `https://aec.cintoo.com/oauth/token`                                                                                                                                         |
    | State                                  | use a random code made of `[a-z0-9]+` with a "correct length". More information about the [state-parameters](https://auth0.com/docs/secure/attack-protection/state-parameters) |
    | Code Challenge Method (only with PKCE) | `SHA-256` more info on [PKCE](https://www.oauth.com/oauth2-servers/pkce/)                                                                                                      |

    ### The flow in detail for acquiring the first Token pair

    This section is here in case you want to **build an oauth client for acquiring the first Token pair without the help of
    the cintoo-login or without 3rd party software**.

    The term "Client" is going to be described as the originator of the oauth token request.

    * Client calls the Cintoo `oauth/authorize` endpoint ***from the browser*** with the following query parameters:
        * the `redirect_uri` (callback-url) it wants Cintoo to send the authorization code to
        * the `state` uses a random code made of `[a-z0-9]+` with a "correct length". More information about
          the [state-parameters](https://auth0.com/docs/secure/attack-protection/state-parameters)
        * (optional) `code_challenge` a random code that has been hashed using the SHA256 algorithm. More info
          on [PKCE](https://www.oauth.com/oauth2-servers/pkce/).
      > code_verifier = high-entropy cryptographic random STRING using the
      unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
      from Section 2.3 of [RFC3986], with a minimum length of 43 characters
      and a maximum length of 128 characters.
        * (optional but mandatory if `code_challenge` is set) `code_challenge_method` : "SHA256"
    * Cintoo API returns an html element with an `approve` button to be clicked by the user that started the action (the
      same shown in the previous chapter)
    * Once the `approve` button is pressed there is a `Form Post URL redirect` that will target the `redirect_uri` provided
      by the Client with the query parameters : `code` and `state` (+ `code_challenge` and `code_challenge_method` if
      specified before)
    * The Client ***should check*** that the `state` sent and received are the same to avoid CSRF attacks.
    * If they are the same, the Client can then send a `POST` query to the `oauth/token` endpoint with the following payload
      that contains the `code` it received on the previous steps.

      ```json
      {
          "code": "{code}",
          "grant_type": "authorization_code",
          "authorization_code": "{redirect_uri}"
      }
      ```

      or with PKCE

      ```json
      {
          "code": "{code}",
          "grant_type": "authorization_code",
          "authorization_code": "{redirect_uri}",
          "code_verifier": "{code_verifier}"
      }
      ```

    * Finally the Cintoo API answers the `oauth/token` with a json object that contains the `access_token` and
      `refresh_token` pair:

      ```json
      {
          "access_token": "{access_token}",
          "refresh_token": "{refresh_token}",
          "token_type": "Bearer",
          "expires_in": 10800,
          "user_id": "{user_id}"
      }
      ```

    - - -

    ## Doing queries

    Below you will find an example on how to use the token to do a query.

    A good endpoint to start with is the `isLogged` **resource** which simply returns `200` and `{"success": true}` if the
    token was recognized by the Cintoo API.

    We highly recommend that you try it, then start changing the resource type (like `accounts` for instance) to get used to
    the API.

    ### Query example for Windows

    #### with cUrl

    ```sh
    export HOST=aec.cintoo.com
    export TOKEN=...

    curl --url "https://{host}/api/2/accounts" --header "Authorization: Bearer $TOKEN"
    ```

    #### with Python

    ```python
    import requests

    host = "{host}"
    token = "..."

    response = requests.get(
      "https://%s/api/2/accounts" % host,
      headers = { "Authorization": "Bearer %s" % token}
    )
    print(response.status_code)
    print(response.json())
    ```

    #### with Powershell

    Copy the code below and try it in your Powershell terminal.

    > Hint: copy paste this while replacing the `xxxxxxx` with your token, and call as many `Invoke-RestMethod` as you want.
    > Note: the token is surrounded by `"`

    ```powershell
    $token = "xxxxxxxxxxxxxxxxxx"
    $headers = @{
        Authorization="Bearer $token"
        Content='application/json'
    }
    Invoke-RestMethod -Headers $headers -Method Get -Uri "https://aec.cintoo.com/api/isLogged"
    ```

    The powershell command line should return this

    ```powershell
    success
    -------
       True
    ```

    ## URNs

    URNs are standard [Uniform Resource Names](https://fr.wikipedia.org/wiki/Uniform_Resource_Name).

    Our NID is of course "cintoo", so they look like: ```urn:cintoo:{object type}:{object id}```.

    All the endpoints that return an object or a list of objects include a field giving the URN of the object, that you can
    in turn use to reference this object whenever you need to mention it.

    ## References

    Throughout the API, most endpoints need you to give *references* to existing objects,
    like accounts, projects, roles, etc. For example, getting the details of an account will look like:

    ```
    GET /api/2/accounts/{accountRef}
    ```

    A *reference* can be any unambiguous identifier for the object. It typically is either:

    - the raw uuid or numerical id, depending on the type of the object:
        - ```c41aec8b-3b85-4bdd-81e5-c2cfdb8980e8```
        - ```5```
    - a URN (Uniform Resource Name), looking like: ``urn:cintoo:<object type>:<raw uuid or id>``. For example:
        - ```urn:cintoo:account:c41aec8b-3b85-4bdd-81e5-c2cfdb8980e8```
        - ```urn:cintoo:user:5```

    The URN is the most durable option and should be preferred, each object returned by the API has an ```urn``` field
    holding it,
    whereas at some point the raw id/uuid objects fields will be deprecated or removed from the objects content.

    If your client needs to store identifiers for objects, **you should choose the URN**,

    - - -

    ## Errors

    To communicate errors, the Cintoo API

    * fills the HTTP status code defined by the
      standard: [HTTP response status codes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)

    It is consensus in the industry that the ***status code is the source of truth*** on whether an API call was successful
    or not, the ***body is merely a BONUS***.

    ### Error-body returned

    We are following this [RFC](https://www.rfc-editor.org/rfc/rfc9457) whose content is the following

    > * The "status" member is a JSON number indicating The HTTP status code ([HTTP], Section 15) generated by the origin
        server for this occurrence of the problem.
        >
        >   The "status" member, if present, is only advisory; it conveys the HTTP status code used for the convenience of
        the consumer. Generators MUST use the same status code in the actual HTTP response, to assure that generic HTTP
        software that does not understand this format still behaves correctly. See Section 5 for further caveats regarding
        its use.
        >
        >   Consumers can use the status member to determine what the original status code used by the generator was when it
        has been changed (e.g., by an intermediary or cache) and when a message's content is persisted without HTTP
        information. Generic HTTP software will still use the HTTP status code.
    > * The "title" member is a JSON string containing a short, human-readable summary of the problem type.
        >
        >   It SHOULD NOT change from occurrence to occurrence of the problem, except for localization (e.g., using
        proactive content negotiation; see [HTTP], Section 12.1).
        >
        >   The "title" string is advisory and is included only for users who are unaware of and cannot discover the
        semantics of the type URI (e.g., during offline log analysis).
    > * "detail" (string) - The "detail" member is a JSON string containing a human-readable explanation specific to this
        occurrence of the problem.
        >
        >   The "detail" string, if present, ought to focus on helping the client correct the problem, rather than giving
        debugging information.
        >
        >   Consumers SHOULD NOT parse the "detail" member for information; extensions are more suitable and less
        error-prone ways to obtain such information.

    In addition, 2 extension fields are added:

    * `errorCode`: detailed code describing the error, can be used to map to a localized message
    * `errorValues`: values implied in the error, typically an input parameter, can be used to include in a localized
      message

    The mandatory fields that are always present are : `status` and `title`. Others are optional.

    Example:

    ```json
    {
      "status": 404,
      "title": "Not Found",
      "detail": "Account 1234 not found",
      "errorCode": "account-not-found",
      "errorValues": {
        "accountId": "1234"
      }
    }
    ```

    So the HTTP response status code is both in the HTTP header, and in the `status` attribute of the returned JSON object.

    For a list of `errorCode` and `errorValues`, see the dedicated section [Errors List](#tag/errors_list)

    - - -

    ## Permissions

    Each resource needs to have a permission to be accessed.
    Permissions are represented by three elements:

    * *domain*: the level on which the resource is: `tenant`, `account`, `project` or `workzone`
    * *resource*: the type of resource, for example `users` or `tags`
    * *right*: the right on the resource the permission is giving.
      Most of the time it will be either `read` (get/list resource) or `write` (create, update, delete)
      but can be another action when finer granularity is needed.

    This is represented by a string containing those 3 elements separated by a colon: `<domain>:<resource>:<right>`.
    For example `account:users:read` is the permission to *read* (view/list) users on the account level.

    Here is the list of the currently supported permissions:

    * `tenant:user-permissions:read`
    * `tenant:user-permissions:write`
    * `account:account:read`
    * `account:account:update-owner`
    * `account:administrators:read`
    * `account:administrators:write`
    * `account:integrations:read`
    * `account:integrations:write`
    * `account:project-listers:read`
    * `account:project-listers:write`
    * `account:project-managers:read`
    * `account:project-managers:write`
    * `account:projects:read` (implies `project:project:read` on all projects of the account)
    * `account:projects:create`
    * `account:projects:update` (implies `project:project:update-details` on all projects of the account)
    * `account:projects:delete` (implies `project:project:delete` on all projects of the account)
    * `account:roles:read`
    * `account:roles:write`
    * `account:subscriptions:read`
    * `account:subscriptions:update-name`
    * `account:users:read`
    * `account:users:write` (implies `workzone:members:write` on all workzones in the account)
    * `account:groups:read`
    * `project:project:read`
    * `project:project:delete`
    * `project:project:update-details`
    * `project:project:update-owner`
    * `project:project:update-subscription`
    * `project:members:list`
    * `project:annotations:create-integration-link`
    * `project:integrations:read`
    * `project:integrations:write`
    * `workzone:annotations:read`
    * `workzone:annotations:write`
    * `workzone:crops:read`
    * `workzone:crops:write`
    * `workzone:documents:read`
    * `workzone:documents:write`
    * `workzone:export-jobs-reality-data:read`
    * `workzone:export-jobs-reality-data:write`
    * `workzone:import-jobs-reality-data:write`
    * `workzone:measurements:read`
    * `workzone:measurements:write`
    * `workzone:members:write`
    * `workzone:share-links:read`
    * `workzone:share-links:write`
    * `workzone:own-share-links:read`
    * `workzone:own-share-links:write`
    * `workzone:reality-data:read`
    * `workzone:reality-data:write`
    * `workzone:reality-data:transform`
    * `workzone:savedviews:read`
    * `workzone:savedviews:write`
    * `workzone:tags:read`
    * `workzone:tags:write`
    * `workzone:workzones:read`
    * `workzone:workzones:write`
    * `workzone:progress-monitoring-jobs:read`
    * `workzone:progress-monitoring-jobs:write`
    * `workzone:own-progress-monitoring-jobs:read`
    * `workzone:own-progress-monitoring-jobs:write`
    * `workzone:model-reports:read`
    * `workzone:model-reports:write`
    * `workzone:cad-model:read`
    * `workzone:cad-model:write`
    * `workzone:cad-model:transform`
    * `workzone:own-import-jobs-cad-model:read`
    * `workzone:own-import-jobs-cad-model:write`
    * `workzone:import-jobs-cad-model:read`
    * `workzone:import-jobs-cad-model:write`
    * `workzone:video3d:read`
    * `workzone:video3d:write`
    * `workzone:import-video3d:write`

    ### Roles

    A user is given permissions through roles. There are roles on the different levels detailed below.

    #### Roles at the account level

    Roles on an account are currently not modifiable, and are the following:

    * *Account Member* is any user invited to the account
    * *Account Owner*
    * *Account Administrator*
    * *Project Manager*
    * *Project Lister*

    An *account member* has the following permissions:

    * `account:account:read` to view the account
    * `account:roles:read` to view roles on the account
    * `account:groups:read` to view groups on the account

    An *account owner* or *account administrator* are account members, and have the following additional permissions:

    * `account:projects:read` to list and view all projects on the account
    * `account:projects:update` to update any project on the account
    * `account:roles:write` to create, edit and delete roles on the account
    * `account:users:read` to list users members of the account
    * `account:users:write` to invite or remove users on the account
    * `account:subscriptions:read` to view subscriptions of the account
    * `account:subscriptions:update-name` to rename a subscription of the account
    * `account:administrators:read` to list administrators on the account
    * `account:administrators:write` to invite or remove an administrator
    * `account:project-managers:read` to list project managers on the account
    * `account:project-managers:write` to invite or remove a project manager
    * `account:project-listers:read` to list project listers on the account
    * `account:project-listers:write` to invite or remove a project lister
    * `account:integrations:read` to list and view all integrations configured on the account
    * `account:integrations:write` to create, update and delete integrations on the account

    An account owner has also the permission `account:account:update-owner` that an account administrator does not have,
    to set another user as the account owner

    A *project manager* is an account member with the following additional permissions:

    * `account:projects:read` to list and view all projects on the account
    * `account:projects:create` to create new projects
    * `account:projects:update` to update any project
    * `account:projects:delete` to delete any project
    * `account:roles:write` to create, edit and delete roles on the account
    * `account:users:read` to list users of the account
    * `account:users:write` to invite or remove users on the account
    * `account:subscriptions:read` to view subscriptions of the account
    * `account:project-managers:read` to list project managers on the account
    * `account:project-managers:write` to invite or remove a project manager

    A *project lister* is an account member with the following additional permission:

    * `account:projects:read` to list and view all projects of the account

    #### Roles at project and workzone levels

    Roles can be created on an account to give permissions on project and workzone levels.

    Users and groups are invited to a project with a specific role, thus a user has the role assigned to him,
    but also the roles of the groups he belongs to.
    A user's permissions are determined by adding the permissions of all his roles.

    Currently the permissions given to a role are restricted to the following:

    * `project:project:update-details` + `project:project:delete`
      allows the user to manage the project
    * `workzone:workzones:read` + `workzone:workzones:write`
      allows the user to create, modify and delete work zones
    * `workzone:reality-data:read` + `workzone:crops:read` allows the user to view reality data
    * `workzone:reality-data:write` + `workzone:import-jobs-reality-data:write`
      allows the user to import or delete scans
    * `workzone:reality-data:transform`
      allows the user to modify the position and/or rotation of a scan
    * `workzone:export-jobs-reality-data:write`
      allows the user to export and download reality data
    * `workzone:annotations:read` allows the user to view annotations
    * `workzone:annotations:read` + `workzone:annotations:write`
      allows the user to create, edit and delete annotations, and assign notes to team members
    * `workzone:measurements:read` + `workzone:measurements:write` + `workzone:crops:write`
      allows the user to add and view 3D measurements (and add/update crops)
    * `workzone:share-links:read` + `workzone:share-links:write`
      allows the user to create, edit and delete share links
    * `workzone:own-share-links:read` + `workzone:own-share-links:write`
      allows the user to create, edit and delete the share links they created
    * `workzone:documents:read` + `workzone:documents:write`
      allows the user to upload and download documents
    * `workzone:tags:read` allows the user to view tags
    * `workzone:tags:write`
      allows the user to import, create, edit and delete tags
    * `workzone:savedviews:read` allows the user to view saved views
    * `workzone:savedviews:read` + `workzone:savedviews:write`
      allows the user to create, edit and delete saved views
    * `workzone:members:write` allows the user to add and remove team members
    * `workzone:progress-monitoring-jobs:read` + `workzone:progress-monitoring-jobs:write`
        + `workzone:own-progress-monitoring-jobs:read` + `workzone:own-progress-monitoring-jobs:write`
        + `workzone:model-reports:read` + `workzone:model-reports:write`
          allows the user to create, edit, download and delete progress monitoring reports
    * `workzone:cad-model:read` + `workzone:cad-model:write` + `workzone:own-import-jobs-cad-model:read` +
      `workzone:own-import-jobs-cad-model:write` + `workzone:import-jobs-cad-model:read` +
      `workzone:import-jobs-cad-model:write` allow the user to create, edit, download and delete CAD Model and to import
      them from different sources.
    * `workzone:cad-model:transform`
      allows the user to modify the position and/or rotation of a 3D model
    * `workzone:video3d:write` + `workzone:import-video3d:write` allow the user to trigger the Add 360Video job

    Any member of a project also automatically has the following permissions:

    * `project:project:read` and `project:members:list` on the project
    * `workzone:workzones:read` on the workzone he is member and all its child workzones

    A project owner automatically has all permissions on the project and all its workzones.

    A member having permission `workzone:annotations:write` on the root work zone of a project,
    automatically has the following permissions on the project:

    * `project:annotations:create-integration-link`
    * `project:integrations:read`
    * `project:integrations:write`

    - - -

    ## API 1 and API 2

    ### What to expect with API 2

    * Stage 1: API 2 provides features not covered by API 1.\
      Most of features are tagged `BETA` showing that they will continue to mature
    * Stage 2: API 2 `BETA` tags are removed, and API 2 starts covering API 1 features
    * Stage 3: API 2 fully covers API 1 scope
    * Stage 4: API 1 will have a end of life support date communicated
    * Stage 5: API 1 will be decommissioned

    ### Similarities

    * Authentication system is the same. Tokens used on API 1 can be used in API 2.
    * Do follow the same kind of path ex: `/accounts/{accountRef}/projects/{projectRef}/...`

    ### Differences

    * Reference system is different: API 2 handles new Reference types (URNs), classic references (UUID) and "legacy
      references" coming from API 1.\
      API 1 supports UUID and "legacy references" only.
    * Error messages in API 2 follow
      the [RFC 9457 (Problem Details for HTTP APIs)](https://www.rfc-editor.org/rfc/rfc9457).\
      In short `{"code": ...,  "message": ...}` becomes `{"status": ...,  "title": ...}`.
    * All resources returned by API 2 have multiple ref types (always an URN + UUID or INT)
    * POST and PUT endpoints do return full objets (normally available with a GET)
    * Objects returned from API 1 and API 2 differ

    ### Endpoints only in API 1

    * Report Projects Last Accessed Date
    * Report Users Last Activity Date
  license:
    name: Apache 2.0
    url: https://www.apache.org/licenses/LICENSE-2.0.html
  contact:
    name: API Support
    email: support@cintoo.com
  x-logo:
    url: ./assets/logo_api.svg
    altText: Cintoo API
servers:
  - url: https://aec.cintoo.com
security:
  - oauth2: []
tags:
  - name: token_management
    x-displayName: Token Management
    description: |
      ## Good practices

      ### Each user should have their own tokens

      Tokens are personal and are bound to the person who generated them with all their roles and permissions.

      In The Cintoo platform a subscription can hold an unlimited number of users. Furthermore, there are roles (Project Managers) to grant access users to resources with different levels of privileges (read, write, etc.).

      There are only positive outcomes to use as many users as possible.

      ### Single refresher, multiple accessors

      If you absolutely need to have a single user do all the API calls, but multiple applications share the same tokens, you might very quickly hit the limit of simultaneous refresh tokens valid for a single user.

      Remember from the [Access Tokens and Refresh Tokens](#section/API-Specification/Authentication) section that

      * Access Tokens are JWT tokens, that are stateless and can be used in parallel
      * Refresh Tokens are stateful and ***cannot*** be used in parallel

      Our recommendation in that case would be to have

      * A 'refreshing process/app' which would be the only one to use the refresh token and store both Refresh Token and Access Token. The stored Access Token should remain available for other processes
      * Multiple processes/apps that would ask for the Access Tokens, which could be either in the DB **OR** from the 'refreshing process/app'. These requests should be performed intermittently to prevent Access Token expiration, so that the processes/apps can perform all the queries to the Cintoo API

      > ***Note:*** the Access Token is a JWT and can easily be decoded to check the expiration date

      ### Race conditions risk without proper synchronization

      In case that it is not possible to refresh the token from a single app, the client should at least handle race conditions in which a single refresh token is used twice by two concurrent processes. In this case the second refresh will fail. The first client should have stored the new Access-Token/Refresh-Token immediately after the refresh process.
  - name: errors_list
    x-displayName: Errors List
    description: |
      ## 400 Bad Request

      ### Invalid reference

      All resources including a reference, such as `accountRef` or `projectRef`
      will return a status 400 when the reference is invalid.

      #### Malformed URN

      * `errorCode`: `invalid-xxx-urn` where `xxx` is the type of reference

      Example:

      ```json
      {
        "status": 400,
        "title": "Bad Request",
        "detail": "ID type mismatch in Urn. Expected a 'user', found a 'role'",
        "errorCode": "invalid-user-urn"
      }
      ```

      ### Invalid input

      The error code `invalid-input` is used when an element of the request is invalid,
      for example updating a project with a too long name.

      ### Specific cases

      Each endpoint may have more specific error cases for input validation,
      for which you'll find the list and description in their dedicated section.

      ## 401 Unauthorized

      All endpoints, except authentication endpoints, need to be authenticated.
      The status 401, with `errorCode` = `unauthorized` is returned if the request
      is done without authentication or with an invalid or expired authentication

      ## 403 Forbidden

      ### Action forbidden

      Entry points doing a creation, an update, or a deletion return a status
      403 when the action is forbidden for the user.

      * `errorCode`: `xxx-yyy-forbidden` where:
        * `xxx` is the type of action, which is one of `create`, `update`, or `delete`
        * `yyy` is the type of resource on which the action is requested,
        for example `role`, `user`, ...
      * `errorValues`:
        * `requiredPermissions`: list of permissions

      Example:

      ```json
      {
        "status": 403,
        "title": "Forbidden",
        "detail": "You do not have the permission to delete project. It requires to have permission \"project:project:delete\" or \"account:projects:delete\"",
        "errorCode": "delete-project-forbidden",
        "errorValues": {
          "requiredPermissions": ["project:project:delete", "account:projects:delete"]
        }
      }
      ```

      If the action is performed on a deleted element, for example create a new work zone on a deleted project,
      the error code `deleted-xxx` is returned with xxx the type of element on which the action is performed.

      Example:

      ```json
      {
        "status": 403,
        "title": "Forbidden",
        "detail": "Access forbidden because workzone is deleted",
        "errorCode": "deleted-workzone",
        "errorValues": {
          "workzoneId": "12345"
        }
      }
      ```


      ### Specific cases

      The following `errorCode` values can be returned with status 403:

      * `not-member-of-project` when a user tries to access to a project he is not a member of
      * `not-contributor-of-project` when a user tries to access to a work zone of a project he is not a contributor of
      * `list-*-forbidden` when trying to list resources without the related permissions
      * `view-*-forbidden` when trying to view a resource without needed permissions
      * `remove-contributor-from-work-zone-forbidden` when trying to remove a user as a contributor from a work zone
      without needed permissions

      ## 404 Not Found

      ### Reference not found

      All resources including a reference, such as `accountRef` or `projectRef`
      will return a status 404 when the reference is valid but does not exist.

      * `errorCode`: `xxx-not-found` where `xxx` is the type of reference
      * `errorValues`:
        * `xxx`: the ID of the reference, where `xxx` is the type of reference

      Example:

      ```json
      {
          "status": 404,
          "title": "Not Found",
          "detail": "Account 1234 not found",
          "errorCode": "account-not-found",
          "errorValues": {
              "account": "1234"
          }
      }
      ```

      ### Specific cases

      The following `errorCode` values can be returned with status 404:

      * `user-email-not-found` when referencing a user referenced by its email was not found in the database
  - name: tuto_upload_file
    x-displayName: Upload a file into Cloud storage
    description: |-
      Objective is to provide recipe on how to upload a file (document, cover...)

      ### Behavior
      For security purpose, the files are not going through Cintoo servers directly. 
      The Cintoo server will provide you a secured url to let you upload the file directly to the storage defined by the region of the selected project.
      After uploading the file to the cloud storage, you have to inform Cintoo platform that the file has been uploaded successfully, and it should be registered inside your project.
      Not performing this part will not guarantee that the uploaded file won't be deleted by background cleanup tasks.

      ### Steps

      #### 1. Get a signed URL
      The first step is to get from Cintoo Platform an url to which you can upload your file. The endpoint may differ regarding the type fo file you want to upload (since the permissions are not the same)

      * For a project cover (only one file per upload):
      [`POST /api/2/accounts/{accountRef}/projects/{projectRef}/cover`](#tag/Project/operation/signProjectCoverURL)

      * For a crop cover (only one file per upload): 
      [`POST /api/2/accounts/{accountRef}/projects/{projectRef}/crops/{cropRef}/cover`](#tag/File/operation/signCropCoverURL)

      * For another file (many files upload is supported): 
      [`POST /api/2/accounts/{accountRef}/projects/{projectRef}/workzones/{workzoneRef}/bulk-files`](#tag/File/operation/signUploadURL)

      In the body of the response, you need to provide for each file you want to upload:
      * the name
      * the extension (not in the case of cover upload)
      * the size
      * the md5 sum-check of the content

      This call will return an url attribute for each file to be used to upload the content on the cloud storage

      #### 2. Upload the file
      Use the provided url during previous step to upload the file

      There are different ways to upload file to cloud storage. Here below to link to the different documentation:
      * [S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/upload-objects.html)
      * [Azure](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction)

      **Required headers to push the files**
      ```
      Content-MD5: xxx with xxx being a base 64 encode of the md5sum in binary format.
      Example: MD5SUM="$(openssl md5 -binary < "$FILENAME" | base64)"
      Cache-Control: max-age=2592000
      x-ms-blob-type: BlockBlob mandatory if the storage type is Azure.
      Note: always adding the x-ms-blob-type could be a good idea as Aws ignores it anyway.
      ```

      #### 3. Update the file status into Cintoo workzone
      The last call is to propagate the upload status to Cintoo Platform. This is done by calling a specific endpoint regarding the type of file.

      * For a project cover (only one file per upload):
      [`PUT /api/2/accounts/{accountRef}/projects/{projectRef}/cover`](#tag/Project/operation/updateProjectCover)

      * For another file (many files upload is supported): 
      [`POST /api/2/accounts/{accountRef}/projects/{projectRef}/workzones/{workzoneRef}/bulk-files`](#tag/File/operation/registerFiles)

      In the body of this call, you need to provide the same data as for the 1st call (POST).

      This call will return an id attribute for each file, which is an unique identifier for the file in the Cintoo platform.
  - name: understand_tag_data
    x-displayName: Understand Tag Data
    description: |+
      There are two categories of tags:
      * Simple ones, that are composed of a single bounding box,
        are usually used to tag something simple like a motor or a pump
      * Complex ones, that are composed of multiple bounding boxes,
        are usually used to tag something with a complex shape or multiple elements,
        like a pipeline or a conveyor belt

      ### Simple Tag

      #### Axis-Aligned Bounding Box (AABB)

      The simplest possible Tag, composed of a single bounding box, aligned with the world axis X, Y and Z.
      It's data contains a `boundingBox` property:
      | Property | Type | Description |
      | -------- | ---- | ----------- |
      | `boundingBox` | 6 numbers | Coordinates of the min and max corners of the tag.<br/>In the order: `minX`, `minY`, `minZ`, `maxX`, `maxY`, `maxZ`, in meters. |

      #### Oriented Bounding Box (OBB)

      Another simple tag, composed of two boxes:
      * `orientedBoundingBox` is the main one, defining the real volume of the tag
      * `boundingBox` is the AABB surrounding the OBB

      | Property | Type | Description                                                                                                                                                                                                                                                                                                                                                                                                                        |
      | -------- | ---- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
      | `orientedBoundingBox` | 10 numbers | Ten numbers representing an Oriented Bounding Box.<br>In the order:<ul><li>`posX`, `posY` and `posZ` represent the center of the bounding box<br>in meters</li><li>`quatX`, `quatY`, `quatZ` and `quatW` represent the quaternion<br>defining the rotation of the bounding box, using the center of<br>the box as a pivot point</li><li>`sizeX`, `sizeY` and `sizeZ` represent the size of the bounding<br>box in meters</li></ul> |
      | `boundingBox` | 6 numbers | Coordinates of the min and max corners of the Axis-Aligned Bounding Box<br>englobing the defined Oriented Bounding Box.<br>In the order: `minX`, `minY`, `minZ`, `maxX`, `maxY`, `maxZ`, in meters.                                                                                                                                                                                                                                |

      ### Complex Tag

      They have two properties: `boundingBox` and `model`.
      | Property | Type | Description |
      | -------- | ---- | ----------- |
      | `boundingBox` | 6 numbers | Coordinates of the min and max corners of the Axis-Aligned Bounding Box<br>englobing the whole Complex Tag.<br/>In the order: `minX`, `minY`, `minZ`, `maxX`, `maxY`, `maxZ`, in meters. |
      | `model` | `object` | Describe a list of bounding boxes defining the tag. It always contains a<br> `type` property, defining the type of complex tag. Other properties<br> depend on the value of this type and are described in the following sections. |

      #### Axis-Aligned Bounding Boxes only

      This format is used if there are only Axis-Aligned Bounding Boxes composing the Complex tag.

      | Property | Type        | Description                                                                                                                                                                                                                  |
      | -------- |-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
      | `type` | `String` | `AxisAlignedList`                                                                                                                                                                                                            |
      | `list` | `number[][]` | An array containing multiple sub-arrays. Each sub-array contains the coordinates of the<br> min and max corners of an Axis-Aligned Bounding Box.<br>In the order: `minX`, `minY`, `minZ`, `maxX`, `maxY`, `maxZ`, in meters. |

      #### Oriented Bounding Boxes

      Two types are available to represent oriented bounding boxes, with the second type being an optimized version for storage purposes only.

      ##### Independent Oriented Bounding Boxes

      This format is used if there are only Oriented Bounding Boxes composing the Complex tag, with each of them having a different orientation. If an orientation is shared, see the next Shared Oriented Bounding Boxes section.

      | Property | Type | Description                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
      | -------- | ---- |--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
      | `type` | `String` | `OrientedList`                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
      | `orientedBoundingBoxList` | `number[][]` | An array containing multiple sub-arrays. Each sub-array contains ten<br> numbers representing an Oriented Bounding Box.<br>In the order:<ul><li>`posX`, `posY` and `posZ` represent the center of the<br> bounding box in meters</li><li>`quatX`, `quatY`, `quatZ` and `quatW` represent the<br> quaternion defining the rotation of the bounding box,<br> using the center of the box as a pivot point</li><li>`sizeX`, `sizeY` and `sizeZ` represent the size of the<br> bounding box, in meters</li></ul> |

      ##### Shared Oriented Bounding Boxes

      This is an optimization of Independent Oriented Bounding Boxes, for boxes with a similar orientation.

      | Property | Type | Description                                                                                                                                                                                                                                                                                                                                                                                                     |
      | -------- | ---- |-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
      | `type` | `String` | `OrientedList`                                                                                                                                                                                                                                                                                                                                                                                                  |
      | `groupedOBBList` | `number[][]` | An array containing multiple sub-arrays. Each sub-array contains numbers, in order:<ul><li>`quatX`, `quatY`, `quatZ`, `quatW` is the shared rotation, represented<br> by a quaternion, with the pivot being the center of each box</li><li>the remaining numbers go 6 by 6: `posX`, `posY`, `posZ`, `sizeX`, `sizeY`, `sizeZ`<br> for each box, with the position and the size of each box, in meters</li></ul> |

      ##### Mix of all

      Apart from BinarizedBoundingBox, all interfaces under “Complex Tags” can be mixed together, under the OrientedList type.

      | Property | Type              |
      | -------- |-------------------|
      | `type` | `OrientedList`    |
      | `list` | `[number x 6][]`  |
      | `orientedBoundingBoxList` | `[number x 10][]` |
      | `groupedOBBList` | `number[][]`      |

      #### Binarized Axis-Aligned Bounding Boxes

      This format is an internal format from Cintoo, intended for use within Cintoo only. It is only constituted by AABBs.


paths:
  /api/2/accounts:
    get:
      summary: List accounts
      operationId: getAccounts
      description: <span class='tag beta'>BETA</span>List available accounts for the current user
      tags:
        - Account
      responses:
        '200':
          description: List and content of the Accounts
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Account'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...

            curl --url "https://aec.cintoo.com/api/2/accounts" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts" % host,
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const token = "...";

            const url = `https://${host}/api/2/accounts`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}:
    get:
      summary: Get account
      operationId: getAccount
      description: |
        <span class='tag beta'>BETA</span>Get an account details

        Requires the permission `account:account:read` on the account (in other words to be a member of the account).
      tags:
        - Account
      parameters:
        - $ref: '#/components/parameters/accountRef'
      responses:
        '200':
          description: Content of the requested Account
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Account'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID" --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s" % (host, accountUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/subscriptions:
    get:
      summary: List subscriptions
      operationId: getSubscriptions
      description: |
        <span class='tag beta'>BETA</span>List available subscriptions into this account.

        Requires the permission `account:subscriptions:read` on the account.

        The `inviteAccountOwnerInNewProjects` property is only included when the caller is the account owner.

        The `end` property is only included when the caller has the `account:subscriptions:read-sensitive-infos` permission (account owner or administrator).
      tags:
        - Subscription
      parameters:
        - name: accountId
          required: true
          $ref: '#/components/parameters/accountRef'
      responses:
        '200':
          description: List and content of the Subscriptions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Subscription'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/subscriptions" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/subscriptions"
                % (host, accountUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/subscriptions`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/subscriptions/{subscriptionRef}:
    get:
      summary: Get subscription
      operationId: getSubscription
      description: |
        <span class='tag beta'>BETA</span>Get a subscription details.

        Requires the permission `account:subscriptions:read` on the account.

        The `inviteAccountOwnerInNewProjects` property is only included when the caller is the account owner.

        The `end` property is only included when the caller has the `account:subscriptions:read-sensitive-infos` permission (account owner or administrator).
      tags:
        - Subscription
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/subscriptionRef'
      responses:
        '200':
          description: Content of the requested Subscription
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Subscription'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export SUBSCRIPTIONID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/subscriptions/$SUBSCRIPTIONID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            subscriptionId = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/subscriptions/%s"
                % (host, accountUUID, subscriptionId),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const subscriptionId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/subscriptions/${subscriptionId}`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/subscriptions/{subscriptionRef}/usage-report:
    get:
      summary: Generate an Usage Report
      operationId: getUsageReport
      description: <span class='tag beta'>BETA</span>- Generate an Usage Report, listing actions done per day, per user, per project on a given subscription.
      tags:
        - Usage Report
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/subscriptionRef'
        - in: query
          name: from
          schema:
            $ref: '#/components/schemas/date'
          description: The date to start to retrieve usage report data from (included)
        - in: query
          name: until
          schema:
            $ref: '#/components/schemas/date'
          description: The date to start to retrieve usage report data from (included)
        - in: query
          name: includeDeleted
          schema:
            type: boolean
          description: Also export deleted (including permanently deleted) projects
      responses:
        '200':
          description: Redirection to a pre-signed download link, in the Location header
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/UsageReport'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export SUBSCRIPTIONUUID=...

            curl --location "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/subscriptions/$SUBSCRIPTIONUUID/usage-report?from=2025-09-29&includeDeleted=true" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            subscriptionUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/subscriptions/%s/usage-report?from=2025-09-29&includeDeleted=true"
                % (host, accountUUID, subscriptionUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const subscriptionUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/subscriptions/${subscriptionUUID}/usage-report?from=2025-09-29&includeDeleted=true`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/users/bulk-invite:
    post:
      summary: Invite users
      operationId: inviteUsers
      description: |
        <span class='tag beta'>BETA</span>Invite users to an account

        This endpoint is *idempotent* which means it applies the state requested by the caller.
        It can be used to invite users to an account with a set of accountRole
        or it can be used to grant new accountRoles to existing users.

        Users that are new to the Cintoo cloud platform are expected to receive an invitation email where they will be able to define a password.

        Requires the caller:
        * to have permission `account:administrators:write` to invite users as administrators
        * to have permission `account:project-managers:write` to invite users as project managers
        * to have permission `account:project-listers:write` to invite users as project listers
        * to have permission `workzone:members:write` to invite users without specific account role

        If any of the roles is invalid or if some roles are not allowed for the caller, then the whole request returns an error,
        no invitation are sent and no change is applied.
      tags:
        - User
      parameters:
        - $ref: '#/components/parameters/accountRef'
      requestBody:
        content:
          application/json:
            schema:
              type: array
              items:
                type: object
                required:
                  - email
                properties:
                  email:
                    type: string
                  roles:
                    type: array
                    items:
                      $ref: '#/components/schemas/AccountRoles'
                    uniqueItems: true
      responses:
        '201':
          description: User(s) have been successfully invited with requested AccountRoles
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/users/bulk-invite" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '[
                { "email":"admin@example.com", "roles": ["administrator"] },
                { "email":"manager@example.com", "roles": ["administrator", "projectManager"] },
                { "email":"joe@example.com" }
              ]'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/users/bulk-invite"
                % (host, accountUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = [
                { "email":"admin@example.com", "roles": ["administrator"] },
                { "email":"manager@example.com", "roles": ["administrator", "projectManager"] },
                { "email":"joe@example.com" }
              ]
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/users/bulk-invite`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                [
                    { "email":"admin@example.com", "roles": ["administrator"] },
                    { "email":"manager@example.com", "roles": ["administrator", "projectManager"] },
                    { "email":"joe@example.com" }
                  ]
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/users:
    get:
      summary: List users
      operationId: getUsers
      description: |
        <span class='tag beta'>BETA</span>List available users into this account.
        Data is anonymized for not confirmed users.

        Requires the caller to have one of following permissions: 
        * `account:users:read`
        * `workzone:members:write` on any work zone inside the account
      tags:
        - User
      parameters:
        - name: accountRef
          required: true
          $ref: '#/components/parameters/accountRef'
      responses:
        '200':
          description: List and content of the Users
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/users" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/users"
                % (host, accountUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/users`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/users/{userRef}:
    get:
      summary: Get user details
      operationId: getUser
      description: |
        <span class='tag beta'>BETA</span>Details about a given user into this account.
        Data is anonymized in case the user is not confirmed.

        Requires the caller to have one of following:
        * `account:users:read`
        * `workzone:members:write` on any work zone inside the account
      tags:
        - User
      parameters:
        - name: accountRef
          required: true
          $ref: '#/components/parameters/accountRef'
        - name: userRef
          required: true
          $ref: '#/components/parameters/userRef'
      responses:
        '200':
          description: Details about the user into this account.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/User'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export USERREF=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/users/$USERREF" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            userRef = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/users/%s"
                % (host, accountUUID, userRef),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const userRef = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/users/${userRef}`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/groups:
    get:
      summary: List groups
      operationId: getGroups
      description: |
        <span class='tag beta'>BETA</span>List available groups in this account.

        Requires the permission `account:groups:read` on the account.
      tags:
        - Group
      parameters:
        - $ref: '#/components/parameters/accountRef'
      responses:
        '200':
          description: List and content of the Groups
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Group'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/groups" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/groups"
                % (host, accountUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/groups`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/groups/{groupRef}:
    get:
      summary: Get group
      operationId: getGroup
      description: |
        <span class='tag beta'>BETA</span>Get a group details.

        Requires the permission `account:groups:read` on the account.
      tags:
        - Group
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/groupRef'
      responses:
        '200':
          description: Content of the requested Group
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Group'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export GROUPUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/groups/$GROUPUUID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            groupUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/groups/%s"
                % (host, accountUUID, groupUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const groupUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/groups/${groupUUID}`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/groups/{groupRef}/members:
    post:
      summary: Add users to a group
      operationId: addUsersToGroup
      description: <span class='tag beta'>BETA</span>Add a list of users to a group
      tags:
        - Group
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/groupRef'
      requestBody:
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/UserRef'
      responses:
        '200':
          description: new state of the modified group
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Group'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export GROUPUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/groups/$GROUPUUID/members" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "[\"userRef1\", \"userRef2\"]"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            groupUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/groups/%s/members"
                % (host, accountUUID, groupUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = ["userRef1", "userRef2"]
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const groupUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/groups/${groupUUID}/members`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                ["userRef1", "userRef2"]
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    delete:
      summary: Remove users from a group
      operationId: removeUsersFromGroup
      description: <span class='tag beta'>BETA</span>Removes a list of users from a group
      tags:
        - Group
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/groupRef'
      requestBody:
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/UserRef'
      responses:
        '200':
          description: new state of the modified group
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Group'
        '400':
          description: users not in the group
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorBadRequest'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        description: |
                          * `users-not-in-groups` some of the users were in not in the group
                      errorValues:
                        type: object
                        properties:
                          users:
                            type: string
                            description: list of the users that were not in the group
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export GROUPUUID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/groups/$GROUPUUID/members" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "[\"userRef1\", \"userRef2\"]"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            groupUUID = "..."
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/groups/%s/members"
                % (host, accountUUID, groupUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = ["userRef1", "userRef2"]
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const groupUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/groups/${groupUUID}/members`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                ["userRef1", "userRef2"]
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/roles:
    get:
      summary: List Roles
      operationId: getRoles
      description: |
        <span class='tag beta'>BETA</span>List available roles into this account.

        Requires the permission `account:roles:read`.
      tags:
        - Role
      parameters:
        - $ref: '#/components/parameters/accountRef'
      responses:
        '200':
          description: List and content of the Roles
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Role'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/roles" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/roles"
                % (host, accountUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/roles`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    post:
      summary: Create Role
      operationId: createRole
      description: |
        <span class='tag beta'>BETA</span>Create a custom role.

        Requires the permission `account:roles:write`.

        The allowed combinations of permissions are documented [here](#section/API-Specification/Permissions)
      tags:
        - Role
      parameters:
        - $ref: '#/components/parameters/accountRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/roleCreate'
      responses:
        '201':
          description: The role was successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Role'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/roles" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                "name": "Viewer",
                "description": "Read only user",
                "color": "#123456",
                "permissions": ["view3d", "measure", "view_tag"]
            }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/roles"
                % (host, accountUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "name": "Viewer",
                "description": "Read only user",
                "color": "#12345e",
                "permissions": ["view3d", "measure", "view_tag"]
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/roles`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                    "name": "Viewer",
                    "description": "Read only user",
                    "color": "#123456",
                    "permissions": ["view3d", "measure", "view_tag"]
                }
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/members:
    delete:
      summary: Remove users from an account
      operationId: removeUsersFromAccount
      description: |
        <span class='tag beta'>BETA</span>Remove users from an account

        Requires to be either owner, administrator or manager of the account.

        Notes:
          - The Account Owner cannot be removed from the account. An account cannot be orphaned.
          - Project Owners cannot be removed from the account. Projects cannot be orphaned.
      tags:
        - Account
      parameters:
        - $ref: '#/components/parameters/accountRef'
      requestBody:
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/UserUrn'
              example:
                - userRef1
                - userRef2
      responses:
        '204':
          description: The users were successfully removed from the account
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorForbidden'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        description: |
                          * `remove-user-from-account-forbidden`
              examples:
                remove-user-from-account-forbidden:
                  value:
                    status: 403
                    title: Forbidden
                    detail: You do not have the permission to remove users from this account. It requires to be owner, administrator or manager of the account
                    errorCode: remove-user-from-account-forbidden
                remove-owner-from-account-forbidden:
                  value:
                    status: 403
                    title: Forbidden
                    detail: Account Owner cannot be removed from the account. An account must always have a owner.
                    errorCode: delete-account-owner-forbidden
                    errorValues:
                      - user: ownerId
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTID=...
            export USERID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTID/members" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '[ "$USERID" ]'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountID = "..."
            userId = "..."
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/members" % (host, accountID),
              headers = { "Authorization": "Bearer %s" % token},
              json = [ userId ]
            )
            print(response.status_code)
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountId = "...";
            const token = "...";
            const userId = "...";

            const url = `https://${host}/api/2/accounts/${accountId}/members`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                [ userId ]
              ),
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/accounts/{accountRef}/members/{userRef}/roles:
    post:
      summary: Update the account roles of a user
      operationId: updateUserAccountRoles
      description: |
        <span class='tag beta'>BETA</span>Update the account roles of a user

        Requires to be either owner or administrator of the account.

        To grant/revoke an account role, the permissions are checked:
        - For administrator: `account:administrators:write`
        - For project manager: `account:project-managers:write`
        - For project lister: `account:project-listers:write`
      tags:
        - Account
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/userRef'
      requestBody:
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/AccountRoles'
      responses:
        '204':
          description: the roles have been correctly updated
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorForbidden'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        description: |
                          * `update-user-account-roles-forbidden`
              examples:
                update-user-account-roles-forbidden:
                  value:
                    status: 403
                    title: Forbidden
                    detail: You do not have the permission to update user-account-roles
                    errorCode: update-user-account-roles-forbidden
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTID=...
            export USERID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTID/members/$USERID/roles" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '["administrator", "projectManager", "projectLister"]'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountID = "..."
            userID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/members/%s/roles" % (host, accountID, userID),
              headers = { "Authorization": "Bearer %s" % token },
              json = ["administrator", "projectManager", "projectLister"]
            )
            print(response.status_code)
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountId = "...";
            const userId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountId}/members/${userId}/roles`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                ["administrator", "projectManager", "projectLister"]
              ),
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/accounts/{accountRef}/roles/{roleRef}:
    get:
      summary: Get Role
      operationId: getRole
      description: |
        <span class='tag beta'>BETA</span>Get a role details.

        Requires the permission `account:roles:read`.
      tags:
        - Role
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/roleRef'
      responses:
        '200':
          description: Content of the requested Role
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Role'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export ROLEID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/roles/$ROLEID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            roleId = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/roles/%s"
                % (host, accountUUID, roleId),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const roleId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/roles/${roleId}`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    delete:
      summary: Delete Role
      operationId: deleteRole
      description: |
        <span class='tag beta'>BETA</span>Delete a Role.

        Requires the permission `account:roles:write`.
      tags:
        - Role
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/roleRef'
      responses:
        '204':
          description: The role was successfully deleted
        '400':
          description: Cannot delete base role, or role is used in projects
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorBadRequest'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        description: |
                          * `cannot-delete-base-role` if the role is a base role
                          * `role-used` if the role is used in projects
                      errorValues:
                        type: object
                        properties:
                          roleId:
                            type: string
                            description: role id for errorCode `role-used`
                          projectIds:
                            type: string
                            description: comma separated list of projects using the role for errorCode `role-used`
              examples:
                cannot-delete-base-role:
                  value:
                    status: 400
                    title: Bad Request
                    detail: Base roles can't be deleted
                    errorCode: cannot-delete-base-role
                role-used:
                  value:
                    status: 400
                    title: Bad Request
                    detail: Role 1234 is in use in projects projectId1, projectId2, projectId3
                    errorCode: role-used
                    errorValues:
                      roleId: '1234'
                      projectIds: projectId1, projectId2, projectId3
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export ROLEID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/roles/$ROLEID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            roleId = "..."
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/roles/%s"
                % (host, accountUUID, roleId),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const roleId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/roles/${roleId}`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    put:
      summary: Update Role
      operationId: updateRole
      description: |
        <span class='tag beta'>BETA</span>Update a role.

        <span class='block' style='background-color: PapayaWhip;'><span class='icon' style='color: red; font-size:20px;'>&#9888;</span> Overwrites all fields. Missing fields will be considered as null and current values will be erased</span>

        Permissions:

        Requires the permission `account:roles:write`.

        The allowed combinations of permissions are documented [here](#section/API-Specification/Permissions)
      tags:
        - Role
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/roleRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/roleUpdate'
      responses:
        '200':
          description: New content of the updated Role
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Role'
        '400':
          description: Cannot update base role
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorBadRequest'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        description: '`cannot-update-base-role` if the role is a base role'
              examples:
                cannot-update-base-role:
                  value:
                    status: 400
                    title: Bad Request
                    detail: Base roles can't be updated
                    errorCode: cannot-update-base-role
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export ROLEID=...

            curl --request PUT --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/roles/$ROLEID" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                  "description": "Better description",
                  "color": "#E529B0"
              }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            roleId = "..."
            token = "..."

            response = requests.put(
              "https://%s/api/2/accounts/%s/roles/%s"
                % (host, accountUUID, roleId),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "description": "Better description",
                "color": "#E529B0"
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const roleId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/roles/${roleId}`;
            const res = await fetch(url, {
              method: "PUT",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                      "description": "Better description",
                      "color": "#E529B0"
                  }
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/integrations:
    get:
      summary: List integrations on the account
      operationId: getAccountIntegrations
      description: |
        <span class='tag beta'>BETA</span>List all integrations on the account

        Requires the permission `account:integrations:read` automatically granted for account administrators and account owner.
      tags:
        - Integrations
      parameters:
        - $ref: '#/components/parameters/accountRef'
      responses:
        '200':
          description: List of integrations
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountIntegrations'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/integrations" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/integrations"
                % (host, accountUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/integrations`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/integrations/konekt:
    post:
      summary: Create an account integration with Konekt
      operationId: createAccountIntegrationKonekt
      description: |
        <span class='tag beta'>BETA</span>Configure an integration on the account with Newforma Konekt.

        Requires the permission `account:integrations:write` automatically granted for account administrators and account owner.
      tags:
        - Konekt
      parameters:
        - $ref: '#/components/parameters/accountRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/accountIntegrationKonektCreate'
      responses:
        '201':
          description: The integration was successfully created or updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountIntegrationKonekt'
        '409':
          description: A configuration already exists with the same Konekt Hub
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorConflict'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/integrations/konekt" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "{\"token\":\"...\"}"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/integrations/konekt"
                % (host, accountUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "token": "..."
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/integrations/konekt`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {"token":"..."}
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/integrations/konekt/{integrationRef}:
    get:
      summary: Get an account integration with Konekt
      operationId: getKonektAccountIntegrationById
      description: |
        <span class='tag beta'>BETA</span>Get an integration with Newforma Konekt configured on the account

        Requires the permission `account:integrations:read` automatically granted for account administrators and account owner.
      tags:
        - Konekt
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/accountIntegrationKonektRef'
      responses:
        '200':
          description: account integration to Konekt
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountIntegrationKonekt'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export INTEGRATIONUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/integrations/konekt/$INTEGRATIONUUID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            integrationUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/integrations/konekt/%s"
                % (host, accountUUID, integrationUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const integrationUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/integrations/konekt/${integrationUUID}`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
    put:
      summary: Update an account integration with Konekt
      operationId: updateKonektAccountIntegration
      description: |
        <span class='tag beta'>BETA</span>Update the configuration for the integration with Newforma Konekt.

        Requires the permission `account:integrations:write` automatically granted for account administrators and account owner.
      tags:
        - Konekt
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/accountIntegrationKonektRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/accountIntegrationKonektUpdate'
      responses:
        '200':
          description: The integration was successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountIntegrationKonekt'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export INTEGRATIONUUID=...

            curl --request PUT --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/integrations/konekt/$INTEGRATIONUUID" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "{\"options\":{\"allowCreateNewUsersOnHub\":false}}"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            integrationUUID = "..."
            token = "..."

            response = requests.put(
              "https://%s/api/2/accounts/%s/integrations/konekt/%s"
                % (host, accountUUID, integrationUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "options": {"allowCreateNewUsersOnHub": False}
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const integrationUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/integrations/konekt/${integrationUUID}`;
            const res = await fetch(url, {
              method: "PUT",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {"options":{"allowCreateNewUsersOnHub":false}}
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    delete:
      summary: Delete an account integration with Konekt
      operationId: deleteKonektAccountIntegration
      description: |
        <span class='tag beta'>BETA</span>Delete an integration with Newforma Konekt on the account.

        Requires the permission `account:integrations:write` automatically granted for account administrators and account owner.
      tags:
        - Konekt
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/accountIntegrationKonektRef'
      responses:
        '204':
          description: The integration was successfully deleted
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export INTEGRATIONUUID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/integrations/konekt/$INTEGRATIONUUID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            integrationUUID = "..."
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/integrations/konekt/%s"
                % (host, accountUUID, integrationUUID),
              headers = { "Authorization": "Bearer %s" % token }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const integrationUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/integrations/konekt/${integrationUUID}`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/accounts/{accountRef}/integrations/konekt/{integrationRef}/connection:
    post:
      summary: Connect an account integration with Konekt
      operationId: connectKonektAccountIntegration
      description: |
        <span class='tag beta'>BETA</span>Connect an integration previously disconnected.

        Requires the permission `account:integrations:write` automatically granted for account administrators and account owner.
      tags:
        - Konekt
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/accountIntegrationKonektRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/accountIntegrationConnectionKonektCreate'
      responses:
        '200':
          description: The connection was successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AccountIntegrationKonekt'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export INTEGRATIONUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/integrations/konekt/$INTEGRATIONUUID/connection" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "{\"token\":\"...\"}"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            integrationUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/integrations/konekt/%s/connection"
                % (host, accountUUID, integrationUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "token": "..."
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const integrationUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/integrations/konekt/${integrationUUID}/connection`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {"token":"..."}
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    delete:
      summary: Disconnect an account integration with Konekt
      operationId: deleteKonektAccountIntegrationConnection
      description: |
        <span class='tag beta'>BETA</span>Disconnect an integration without removing it.

        Requires the permission `account:integrations:write` automatically granted for account administrators and account owner.
      tags:
        - Konekt
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/accountIntegrationKonektRef'
      responses:
        '204':
          description: The connection was successfully deleted
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export INTEGRATIONUUID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/integrations/konekt/$INTEGRATIONUUID/connection" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            integrationUUID = "..."
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/integrations/konekt/%s/connection"
                % (host, accountUUID, integrationUUID),
              headers = { "Authorization": "Bearer %s" % token }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const integrationUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/integrations/konekt/${integrationUUID}/connection`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/accounts/{accountRef}/projects:
    get:
      summary: List Projects
      operationId: getProjects
      description: <span class='tag beta'>BETA</span>List active and deleted projects into this account.
      tags:
        - Project
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/countScans'
        - $ref: '#/components/parameters/countTags'
        - $ref: '#/components/parameters/countWorkzones'
      responses:
        '200':
          description: List and content of the Projects
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Project'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...

            # Default: statsInfo includes all statistics
            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects" \
              --header "Authorization: Bearer $TOKEN"

            # Disable all statistics for faster response
            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects?countScans=false&countTags=false&countWorkzones=false" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            token = "..."

            # Default: statsInfo includes all statistics
            response = requests.get(
              "https://%s/api/2/accounts/%s/projects"
                % (host, accountUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            projects = response.json()
            for project in projects:
                print(project["name"], project["statsInfo"])  # {"scanCount": ..., ...}

            # Disable all statistics for faster response
            response = requests.get(
              "https://%s/api/2/accounts/%s/projects"
                % (host, accountUUID),
              headers = { "Authorization": "Bearer %s" % token},
              params = { "countScans": "false", "countTags": "false", "countWorkzones": "false" }
            )
            projects = response.json()
            for project in projects:
                # statsInfo is null when all count* params are false
                print(project["name"], project["statsInfo"])  # None
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const token = "...";

            // Default: statsInfo includes all statistics
            const url = `https://${host}/api/2/accounts/${accountUUID}/projects`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });
            const projects = await res.json();
            for (const project of projects) {
              console.log(project.name, project.statsInfo); // { scanCount, scanSize, tagCount, workzoneCount }
            }

            // Disable all statistics for faster response
            const params = new URLSearchParams({
              countScans: "false",
              countTags: "false",
              countWorkzones: "false",
            });
            const urlFast = `${url}?${params}`;
            const resFast = await fetch(urlFast, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });
            const projectsFast = await resFast.json();
            for (const project of projectsFast) {
              // statsInfo is null when all count* params are false
              console.log(project.name, project.statsInfo); // null
            }
    post:
      summary: Create Project
      operationId: createProject
      description: |
        <span class='tag beta'>BETA</span>Create a project.

        Requires the permission `account:projects:create` on the account.
      tags:
        - Project
      parameters:
        - $ref: '#/components/parameters/accountRef'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                description:
                  type: string
                subscriptionId:
                  $ref: '#/components/schemas/SubscriptionUrn'
                region:
                  type: string
                location:
                  $ref: '#/components/schemas/Location'
                coverUrl:
                  type: string
                coverBlobName:
                  $ref: '#/components/schemas/BlobName'
                spatialReference:
                  type: object
                  nullable: true
                  allOf:
                    - $ref: '#/components/schemas/SpatialReference'
      responses:
        '201':
          description: The project was created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
        '400':
          description: The subscription is not active
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorBadRequest'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        description: |
                          * `subscription-not-active` if the subscription is not active
                          * `region-not-found` if the supplied region name is not known
                      errorValues:
                        type: object
                        properties:
                          subscriptionId:
                            type: string
                            description: Subscription ID for error code `subscription-not-active`
              examples:
                subscription-not-active:
                  value:
                    status: 400
                    title: Bad Request
                    detail: Subscription is not active
                    errorCode: subscription-not-active
                    errorValues:
                      subscriptionId: '1234'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                "name": "created with Wapi",
                "subscriptionId": 1,
                "description": "Hello !",
                "region": "default",
                "location": {
                  "lat": 43.6554,
                  "lng": 7.25
                }
              }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects"
                % (host, accountUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "name": "created with Wapi",
                "subscriptionId": 1,
                "description": "Hello !",
                "region": "default",
                "location": {
                  "lat": 43.6554,
                  "lng": 7.25
                }
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                    "name": "created with Wapi",
                    "subscriptionId": 1,
                    "description": "Hello !",
                    "region": "default",
                    "location": {
                      "lat": 43.6554,
                      "lng": 7.25
                    }
                  }
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}:
    get:
      summary: Get Project
      operationId: getProject
      description: |
        <span class='tag beta'>BETA</span>Get a project details.

        Requires the permission `project:project:read` on the project.
        In other words, the requester MUST already be a member of the project at some level:
        - account owner, administrator, project manager or project lister
        - project creator or owner
        - be a contributor of the project, or be a member of a contributing group
      tags:
        - Project
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/countScans'
        - $ref: '#/components/parameters/countTags'
        - $ref: '#/components/parameters/countWorkzones'
      responses:
        '200':
          description: Content of the requested Project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            # Default: statsInfo includes all statistics
            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID" \
              --header "Authorization: Bearer $TOKEN"

            # Disable scan statistics for faster response
            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID?countScans=false" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            # Default: statsInfo includes all statistics
            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())

            # Disable all statistics for faster response
            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token},
              params = { "countScans": "false", "countTags": "false", "countWorkzones": "false" }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            // Default: statsInfo includes all statistics
            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });
            console.log(res.status);
            const data1 = await res.json();
            console.log(data1);

            // Disable all statistics for faster response
            const params = new URLSearchParams({
              countScans: "false",
              countTags: "false",
              countWorkzones: "false",
            });
            const urlFast = `${url}?${params}`;
            const resFast = await fetch(urlFast, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });
            console.log(res.status);
            const data2 = await res.json();
            console.log(data2);
    delete:
      summary: Delete Project
      operationId: deleteProject
      description: |
        <span class='tag beta'>BETA</span>Delete a project.

        Requires the permission `project:project:delete` on the project.
        (Note that permission `account:projects:delete` on the account implies the permission
        `project:project:delete` on all projects of the account).
      tags:
        - Project
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/permanentProjectDelete'
      responses:
        '204':
          description: The project was deleted successfully
        '400':
          description: The project is already deleted
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorBadRequest'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        description: |
                          * `project-deleted` if the project is already deleted. Add permanent flag to completely delete it.
                          * `project-not-deleted` if asking for a permanent delete on a not deleted project. Delete it first.
              examples:
                project-deleted:
                  value:
                    status: 400
                    title: Bad Request
                    detail: Project is deleted
                    errorCode: project-deleted
                    errorValues:
                      projectId: '1234'
                project-not-deleted:
                  value:
                    status: 400
                    title: Bad Request
                    detail: Project is not deleted yet
                    errorCode: project-not-deleted
                    errorValues:
                      projectId: '1234'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/projects/%s"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
    put:
      summary: Update Project
      operationId: updateProject
      description: |
        <span class='tag beta'>BETA</span>Update a Project. 

        <span class='block' style='background-color: PapayaWhip;'><span class='icon' style='color: red; font-size:20px;'>&#9888;</span> Overwrites all fields. Missing fields will be considered as null and current values will be erased</span>

        Permissions:
        - To update everything: be the account owner, administrator, or project owner
        - To update everything EXCEPT the subscription and the owner: have the "Update, delete or restore projects" permission on the project
        - When updating the owner of a project, the new owner MUST be a project manager
      tags:
        - Project
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/countScans'
        - $ref: '#/components/parameters/countTags'
        - $ref: '#/components/parameters/countWorkzones'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - description
                - subscriptionId
                - ownerId
                - location
                - coverBlobName
                - spatialReference
              properties:
                name:
                  type: string
                  example: New name for my project
                  minLength: 1
                  maxLength: 255
                description:
                  type: string
                  nullable: true
                  maxLength: 1000
                  example: This is a really interesting project
                subscriptionId:
                  $ref: '#/components/schemas/SubscriptionRef'
                ownerId:
                  $ref: '#/components/schemas/UserRef'
                location:
                  type: object
                  nullable: true
                  allOf:
                    - $ref: '#/components/schemas/Location'
                coverBlobName:
                  type: string
                  nullable: true
                  allOf:
                    - $ref: '#/components/schemas/BlobName'
                spatialReference:
                  type: object
                  nullable: true
                  allOf:
                    - $ref: '#/components/schemas/SpatialReference'
      responses:
        '200':
          description: The project was updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
        '400':
          description: Cannot update subscription because capacity is exceeded
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorBadRequest'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        description: |
                          * `capacity-exceeded-scan`, `capacity-exceeded-geoimage`, `capacity-exceeded-tag` if the target
                          subscription cannot accept the project due to a capacity exceeded, respectively of Scan,
                          GeoImage, or Tag
                          * `new-project-owner-not-manager` the new owner of a project needs to be a project manager
                      errorValues:
                        type: object
                        properties:
                          subscriptionId:
                            type: string
                            description: target subscription ID for error codes `capacity-exceeded-xxx`
              examples:
                capacity-exceeded-scan:
                  value:
                    status: 400
                    title: Bad Request
                    detail: not enough Scan capacity to accept project on subscription 1234
                    errorCode: capacity-exceeded-scan
        '403':
          description: Update project ownership or subscription forbidden, or New owner must be a Project Manager
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorForbidden'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        description: |
                          * `update-project-ownership-or-subscription-forbidden`
                          * `new-project-owner-not-manager`
              examples:
                update-project-ownership-or-subscription-forbidden:
                  value:
                    status: 403
                    title: Forbidden
                    detail: You do not have the permission to update project ownership or subscription. It requires to be Project Owner, or Administrator
                    errorCode: update-project-ownership-or-subscription-forbidden
                new-project-owner-not-manager:
                  value:
                    status: 403
                    title: Forbidden
                    detail: New owner must be a Project Manager
                    errorCode: new-project-owner-not-manager
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request PUT --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                "name": "Better name",
                "description": "I love this name more"
              }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.put(
              "https://%s/api/2/accounts/%s/projects/%s"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "name": "Better name",
                "description": "I love this name more"
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}`;
            const res = await fetch(url, {
              method: "PUT",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                    "name": "Better name",
                    "description": "I love this name more"
                  }
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    patch:
      summary: Patch Project
      operationId: patchProject
      description: |
        <span class='tag beta'>BETA</span>Patch a Project. 

        <span class='block' style='background-color: Lavender;'><span class='icon' style='color: blue; font-size:25px;'>&#9432;</span> Unlike the PUT operation (Update a project), the patch updates only the provided fields.</span>

        Permissions:
        - To update everything: be the account owner, administrator, or project owner
        - To update everything EXCEPT the subscription and the owner: have the "Update, delete or restore projects" permission on the project
        - When updating the owner of a project, the new owner MUST be a project manager
      tags:
        - Project
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/countScans'
        - $ref: '#/components/parameters/countTags'
        - $ref: '#/components/parameters/countWorkzones'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              properties:
                name:
                  type: string
                  example: New name for my project
                  minLength: 1
                  maxLength: 255
                description:
                  type: string
                  nullable: true
                  maxLength: 1000
                  example: This is a really interesting project
                subscriptionId:
                  $ref: '#/components/schemas/SubscriptionRef'
                ownerId:
                  $ref: '#/components/schemas/UserRef'
                location:
                  type: object
                  nullable: true
                  allOf:
                    - $ref: '#/components/schemas/Location'
                coverBlobName:
                  type: string
                  nullable: true
                  allOf:
                    - $ref: '#/components/schemas/BlobName'
                spatialReference:
                  type: object
                  nullable: true
                  allOf:
                    - $ref: '#/components/schemas/SpatialReference'
      responses:
        '200':
          description: The project was updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request PATCH --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                "name": "Better name",
                "description": "I love this name more"
              }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.patch(
              "https://%s/api/2/accounts/%s/projects/%s"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "name": "Better name",
                "description": "I love this name more"
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}`;
            const res = await fetch(url, {
              method: "PATCH",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                    "name": "Better name",
                    "description": "I love this name more"
                  }
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/cover:
    post:
      summary: Get a signed URL for project cover file upload
      operationId: signProjectCoverURL
      description: |
        <span class='tag beta'>BETA</span>Get a signed URL for project cover file upload

        Requires the permission `projects:project:update-details`.

        After this call, use the `url`s from the response to upload the cover file.

        Required headers to push the files
        * `Content-MD5: xxx` with `xxx` being a base 64 encode of the md5sum in binary format.\
          Example: `MD5SUM="$(openssl md5 -binary < "$FILENAME" | base64)"`
        * `Cache-Control: max-age=2592000`
        * `x-ms-blob-type: BlockBlob` mandatory if the storage type is Azure. \
          Note: always adding the `x-ms-blob-type` could be a good idea as Aws ignores it anyway.
      tags:
        - Project
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - extension
                - size
                - md5
              properties:
                name:
                  type: string
                extension:
                  type: string
                  enum:
                    - png
                    - jpg
                    - jpeg
                size:
                  type: integer
                  description: Size of the file (bytes)
                  minimum: 1
                  maximum: 10485760
                md5:
                  type: string
                  description: The md5 sum-check of the file
      responses:
        '200':
          description: The signing process succeeded
          content:
            application/json:
              schema:
                type: object
                required:
                  - provider
                  - expiresIn
                  - file
                properties:
                  provider:
                    type: string
                  expiresIn:
                    type: integer
                    description: After this amount of seconds, the upload link won't be available anymore
                  file:
                    type: object
                    required:
                      - name
                      - extension
                      - size
                      - md5
                      - url
                    properties:
                      name:
                        type: string
                      extension:
                        type: string
                      size:
                        type: integer
                        description: Size of the file (bytes)
                      md5:
                        type: string
                        description: The md5 sum-check of the file
                      url:
                        type: string
                        description: the url to which upload the file
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTID=...
            export PROJECTID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTID/projects/$PROJECTID/cover" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                  "name":"cover.gif",
                  "extension":"gif",
                  "size":448459,
                  "md5":"da04845eaedb7db7f5b6f32c4076821f"
                }'

            # -- UPLOAD OF THE FILE --

            # the url received in the body of the response
            export URL=...
            export FILENAME=...

            MD5SUM="$(openssl md5 -binary < "$FILENAME" | base64)"
            curl --request PUT --url "$URL" --header "Content-Type: application/json" -T "$FILENAME" -H "Content-MD5: ${MD5SUM}" -H "Cache-Control: max-age=2592000" -H "x-ms-blob-type: BlockBlob" -i
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountID = "..."
            projectID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/cover"
                % (host, accountID, projectID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                  "name":"tumbleweed.gif",
                  "extension":"gif",
                  "size":448459,
                  "md5":"da04845eaedb7db7f5b6f32c4076821f"
                }
            )
            print(response.status_code)
            print(response.json())

            import base64
            import hashlib

            # the url received in the body of the response
            export url=...
            export filename=...

            hash_md5 = hashlib.md5()
            with open(filename, "rb") as fin:
                for chunk in iter(lambda: fin.read(4096), b""):
                    hash_md5.update(chunk)
            digest = hash_md5.digest()
            md5 = base64.b64encode(digest).decode("utf-8")

            headers = {
              "Content-MD5": md5,
              "Cache-Control": "max-age=2592000",
              "x-ms-blob-type": "BlockBlob", # mandatory if storage type is azure
            }
            data = open(filename, "rb")
            r = requests.put(url, data=data, headers=headers)
            print(r.status_code)
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountId = "...";
            const projectId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountId}/projects/${projectId}/cover`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                      "name":"cover.gif",
                      "extension":"gif",
                      "size":448459,
                      "md5":"da04845eaedb7db7f5b6f32c4076821f",
                    }
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    put:
      summary: Update cloud file as cover for project
      operationId: updateProjectCover
      description: |
        <span class='tag beta'>BETA</span>Update the cover for the project

        Requires the permission `projects:project:update-details`

        This actually adds an already uploaded file as cover for a project
      tags:
        - Project
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - extension
                - size
                - md5
              properties:
                name:
                  type: string
                extension:
                  type: string
                  enum:
                    - png
                    - jpg
                    - jpeg
                size:
                  type: integer
                  description: Size of the file (bytes)
                  minimum: 1
                  maximum: 10485760
                md5:
                  type: string
                  description: The md5 sum-check of the file
      responses:
        '200':
          description: the cover has been updated
          content:
            application/json:
              schema:
                type: object
                required:
                  - name
                  - extension
                  - size
                  - md5
                  - id
                properties:
                  name:
                    type: string
                  extension:
                    type: string
                  size:
                    type: integer
                    description: Size of the file (bytes)
                  md5:
                    type: string
                    description: The md5 sum-check of the file
                  id:
                    $ref: '#/components/schemas/FileUrn'
                    description: the unique id of the file in the Cintoo Platform
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTID=...
            export PROJECTID=...

            curl --request PUT --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTID/projects/$PROJECTID/cover" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                  "name":"tumbleweed.gif",
                  "extension":"gif",
                  "size":448459,
                  "md5":"da04845eaedb7db7f5b6f32c4076821f"
                }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountID = "..."
            projectID = "..."
            token = "..."

            response = requests.put(
              "https://%s/api/2/accounts/%s/projects/%s/cover"
                % (host, accountID, projectID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                  "name":"tumbleweed.png",
                  "extension":"png",
                  "size":448459,
                  "md5":"da04845eaedb7db7f5b6f32c4076821f"
                }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountId = "...";
            const projectId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountId}/projects/${projectId}/cover`;
            const res = await fetch(url, {
              method: "PUT",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                      "name":"tumbleweed.png",
                      "extension":"png",
                      "size":448459,
                      "md5":"da04845eaedb7db7f5b6f32c4076821f"
                    }
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/restore:
    put:
      summary: Restore project
      operationId: restoreProject
      description: |
        <span class='tag beta'>BETA</span>Restore a deleted project.

        Requires the permission `project:project:delete` on the project.
        (Note that permission `account:projects:delete` on the account implies the permission
        `project:project:delete` on all projects of the account).

        Error management:
        If the subscription used by this project does not have enough capacity to restore it,
        a 400 error is returned with a message,
        like "Target subscription <subscriptionId> does not have enough Scan capacity to accept project"
      tags:
        - Project
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/countScans'
        - $ref: '#/components/parameters/countTags'
        - $ref: '#/components/parameters/countWorkzones'
      responses:
        '200':
          description: The project was restored successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Project'
        '400':
          description: The project is not deleted, or the subscription is not active, or subscription capacity exceeded
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorBadRequest'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        description: |
                          * `project-active` if the project is not deleted
                          * `subscription-not-active` if the subscription is not active
                          * `capacity-exceeded-scan`, `capacity-exceeded-geoimage`, `capacity-exceeded-tag` if the target
                          subscription cannot accept the project due to a capacity exceeded, respectively of Scan,
                          GeoImage, or Tag
                      errorValues:
                        type: object
                        properties:
                          projectId:
                            type: string
                            description: Project ID for error code `project-active`
                          subscriptionId:
                            type: string
                            description: Subscription ID for error code `subscription-not-active` or `capacity-exceeded-xxx`
              examples:
                project-active:
                  value:
                    status: 400
                    title: Bad Request
                    detail: Project is active
                    errorCode: project-active
                    errorValues:
                      projectId: '1234'
                subscription-not-active:
                  value:
                    status: 400
                    title: Bad Request
                    detail: Subscription is not active
                    errorCode: subscription-not-active
                    errorValues:
                      subscriptionId: '1234'
                capacity-exceeded-scan:
                  value:
                    status: 400
                    title: Bad Request
                    detail: not enough Scan capacity to accept project on subscription 1234
                    errorCode: capacity-exceeded-scan
                    errorValues:
                      subscriptionId: '1234'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request PUT --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/restore" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.put(
              "https://%s/api/2/accounts/%s/projects/%s/restore"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/restore`;
            const res = await fetch(url, {
              method: "PUT",
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/workzones:
    get:
      summary: List Workzones
      operationId: getWorkzones
      description: <span class='tag beta'>BETA</span>List work zones of a project
      tags:
        - Workzone
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      responses:
        '200':
          description: List of work zones
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Workzone'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/workzones" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/workzones"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/workzones`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    post:
      summary: Create a work zone
      operationId: createWorkzone
      description: |
        <span class='tag beta'>BETA</span>Create a work zone in a project, under an existing one (possibly the root work zone of the project).

        The new work zone automatically inherits the contributors (users and groups) from the parent work zone.

        **Requires** the permission `workzone` on the parent work zone.
      tags:
        - Workzone
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/workzoneCreate'
      responses:
        '201':
          description: The work zone was successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Workzone'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
  /api/2/accounts/{accountRef}/projects/{projectRef}/members:
    get:
      summary: Get Users member of a project
      operationId: getProjectMembers
      description: |
        <span class='tag beta'>BETA</span>GetUsers member of a project.

        Requires the permission `project:members:list`.
      tags:
        - Members
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      responses:
        '200':
          description: List Users part of the Project
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/User'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/members" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/members"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/members`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    post:
      summary: Add members to project
      operationId: addProjectMembers
      description: |
        <span class='tag beta'>BETA</span>Add members to a project.

        #### Role assignment
         * Each member is assigned to a role.
         * A member must not be already assigned to a different role on any work zone of the project.
         * If a member already has the same role, the role is extended to the provided work zones.
         * Each member must be unique in the request.
         * If a user does not exist yet, it will be automatically invited on the account
           (equivalent to the [Invite users](#tag/User/operation/inviteUsers) end-point without account role).

        #### Work zones
         * Specific work zones can be provided to assign the roles only to those work zones **and their sub-workzones**.
         * If the field `workzones` is not provided or null, it is equivalent to provide the topmost work zone of the project,
           in other words the roles are assigned to all work zones of the project.

        #### Permissions
         * Requires a role with "Manage User" permission on the topmost work zone of the project.
         * Requires to have at least all permissions of the provided roles, on the provided work zones.
      tags:
        - Members
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              required:
                - members
              properties:
                workzones:
                  type: array
                  nullable: true
                  items:
                    $ref: '#/components/schemas/WorkzoneRef'
                members:
                  type: object
                  additionalProperties: false
                  properties:
                    users:
                      type: array
                      items:
                        type: object
                        required:
                          - user
                          - role
                        properties:
                          user:
                            type: string
                            description: User's Email
                          role:
                            $ref: '#/components/schemas/RoleRef'
      responses:
        '204':
          description: The users were successfully added to the project
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/members" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                "workzones": [ "urn:cintoo:workzone:...", "urn:cintoo:workzone:..." ],
                "members": {
                  "users": [
                    { "user": "newuser@mycompany.com", "role": "urn:cintoo:role:..." }
                  ]
                }
              }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            workzonesIds = [ "...", "..." ]
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/members"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token},
              json = {
                "workzones": workzonesIds,
                "members": {
                  "users": [ { "user": "newuser@mycompany.com", "role": "..." } ]
                }
              }
            )
            print(response.status_code)
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/members`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                    "workzones": [ "urn:cintoo:workzone:...", "urn:cintoo:workzone:..." ],
                    "members": {
                      "users": [
                        { "user": "newuser@mycompany.com", "role": "urn:cintoo:role:..." }
                      ]
                    }
                  }
              ),
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/accounts/{accountRef}/projects/{projectRef}/members/bulk-remove:
    post:
      summary: Bulk remove users and groups from project
      operationId: removeMembersFromProject
      description: |
        <span class='tag beta'>BETA</span>Remove users and groups from the project.

        This endpoint will ignore users or groups that are not really contributors of the project.

        Permissions:
        Requires a role with "Manage User" permission on the top most work zone of the project.
      tags:
        - Members
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              additionalProperties: false
              properties:
                users:
                  type: array
                  items:
                    $ref: '#/components/schemas/UserRef'
                groups:
                  type: array
                  items:
                    $ref: '#/components/schemas/GroupRef'
      responses:
        '204':
          description: The users and groups were successfully removed from the project
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                    enum:
                      - 403
                  type:
                    type: string
                    enum:
                      - Cannot remove users or groups. Need to have "manage_user" permission on the project
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/members/bulk-remove" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                "users": [ "urn:cintoo:user:...", "urn:cintoo:user:..." ],
                "groups": [ "urn:cintoo:group:...", "urn:cintoo:group:..." ]
              }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            userIds = [ "...", "..." ]
            groupUUIDs = [ "...", "..." ]
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/members/bulk-remove"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token},
              json = {
                "users": userIds,
                "groups": groupUUIDs
              }
            )
            print(response.status_code)
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/members/bulk-remove`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                    "users": [ "urn:cintoo:user:...", "urn:cintoo:user:..." ],
                    "groups": [ "urn:cintoo:group:...", "urn:cintoo:group:..." ]
                  }
              ),
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/accounts/{accountRef}/projects/{projectRef}/members/users/{userRef}:
    delete:
      summary: Remove user from project
      operationId: removeUserFromProject
      description: |
        <span class='tag beta'>BETA</span>Remove a user from the project.

        Requires the permission `workzone:members:write` on the top most work zone of the project.

        **Note** can be invoked on self. Any user can remove himself from a project.
      tags:
        - Members
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/userRef'
      responses:
        '204':
          description: The user was successfully removed from the project
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorForbidden'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        description: |
                          * `remove-contributor-forbidden`
              examples:
                remove-contributor-forbidden:
                  value:
                    status: 403
                    title: Forbidden
                    detail: You do not have the permission to remove contributor. It requires to be targeting self, or to have permission "manage_users"
                    errorCode: remove-contributor-forbidden
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export USERID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/members/users/$USERID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            userId = "..."
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/projects/%s/members/users/%s"
                % (host, accountUUID, projectUUID, userId),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const userId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/members/users/${userId}`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/accounts/{accountRef}/projects/{projectRef}/members/groups/{groupRef}:
    delete:
      summary: Remove group from project
      operationId: removeGroupFromProject
      description: |
        <span class='tag beta'>BETA</span>Remove a group from the project.

        Permissions:

        Requires the permission `workzone:members:write` on the top most work zone of the project.
      tags:
        - Members
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/groupRef'
      responses:
        '204':
          description: The group was successfully removed from the project
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: integer
                    enum:
                      - 403
                  type:
                    type: string
                    enum:
                      - Cannot remove group(s). Need to have "manage_user" permission on the project
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export GROUPUUID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/members/groups/$GROUPUUID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            groupUUID = "..."
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/projects/%s/members/groups/%s"
                % (host, accountUUID, projectUUID, groupUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const groupUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/members/groups/${groupUUID}`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/accounts/{accountRef}/projects/{projectRef}/workzones/{workzoneRef}/members/users/{userRef}:
    delete:
      summary: Remove user from work zone
      operationId: removeUserFromWorkzone
      description: |
        <span class='tag beta'>BETA</span>Remove a user from a work zone and all child work zones.

        If the work zone is not the project's root work zone, and the user to remove is also a contributor on a parent
        work zone, the user must be removed from the parents for which he is a contributor.
        If this is the case, and the query parameter 'allowRemoveOnParents' is not specified or set to false, a bad request
        error will be returned (code 400).

        **Requires** the permission `workzone:members:write` on the work zone
        as well as any parent work zone to which the user is a contributor if the parameter 'allowRemoveOnParents' is true.

        **Note** can be invoked on self. Any user can remove himself from a work zone.
      tags:
        - Workzone
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/workzoneRef'
        - $ref: '#/components/parameters/userRef'
        - $ref: '#/components/parameters/allowRemoveOnParents'
      responses:
        '204':
          description: The user was successfully removed from the work zone
        '400':
          description: The user is a contributor of a parent work zone and the parameter 'allowRemoveOnParents' is not set to 'true'
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorBadRequest'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        description: |
                          * `invalid-input` if the user is a contributor of a parent work zone
                          and the parameter 'allowRemoveOnParents' is not set to 'true'
              examples:
                invalid-input:
                  value:
                    status: 400
                    title: Bad Request
                    detail: 'Invalid parameter allowRemoveOnParents: The user is contributor on a parent work zone and parameter ''allowRemoveOnParents'' is false'
                    errorCode: invalid-input
        '403':
          description: Forbidden
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/ErrorForbidden'
                  - type: object
                    properties:
                      errorCode:
                        type: string
                        description: |
                          * `remove-contributor-from-work-zone-forbidden`
              examples:
                remove-contributor-from-work-zone-forbidden:
                  value:
                    status: 403
                    title: Forbidden
                    detail: You do not have the permission to remove contributor from work zone. It requires to be targeting self, or to be Project Owner, or to have permission "manage_users"
                    errorCode: remove-contributor-from-work-zone-forbidden
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export WORKZONEID=...
            export USERID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/workzones/$WORKZONEID/members/users/$USERID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            workzoneId = "..."
            userId = "..."
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/projects/%s/workzones/%s/members/users/%s"
                % (host, accountUUID, projectUUID, workzoneId, userId),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const workzoneId = "...";
            const userId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/workzones/${workzoneId}/members/users/${userId}`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/accounts/{accountRef}/projects/{projectRef}/files:
    get:
      summary: Get files
      operationId: getFiles
      description: |
        <span class='tag beta'>BETA</span>get all files in project.

        This endpoint accepts a "category" parameter to restrain the categories of the files that will be listed.

        Permissions:
        - the user must have permission `workzone:workzones:read` in work zones in which you can list files.
      tags:
        - File
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/categoryFilter'
      responses:
        '200':
          description: List of files for this project
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/File'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/files" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/files"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/files`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/files/{fileRef}:
    patch:
      summary: Update a file name
      operationId: updateFileName
      description: |
        Update the name of a file.

        Requires write permission on the file workzone:
          * `workzone:reality-data:write` for scans/geo-images/site maps
          * `workzone:cad-model:write` for models
          * `workzone:model-reports:write` for model reports
          * `workzone:documents:write` for documents/media/misc/archive
          * `workzone:video3d:write` for video 360
          * `workzone:workzones:write` for other file types
      tags:
        - File
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/fileRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/fileUpdate'
      responses:
        '200':
          description: The updated file
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/File'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export FILEUUID=...

            curl --request PATCH --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/files/$FILEUUID" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                "filename": "updated-name.jpg"
              }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            fileUUID = "..."
            token = "..."

            response = requests.patch(
              "https://%s/api/2/accounts/%s/projects/%s/files/%s"
                % (host, accountUUID, projectUUID, fileUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "filename": "updated-name.jpg"
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const fileUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/files/${fileUUID}`;
            const res = await fetch(url, {
              method: "PATCH",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify({
                filename: "updated-name.jpg",
              }),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/files/bulk-transformation:
    post:
      summary: Update the position and/or rotation of a scan, model, geoImage or video3d
      operationId: UpdateBulkFilesTransformation
      description: |
        <span class='tag beta'>BETA</span>Update the position, rotation and scale for a list of files, with the following restrictions:
        - the scan, model and geoImage files accept update of only the position and rotation.
        - the video3d files accept update of all three fields.

        Required permissions depend on the type of the files being transformed:
        - scan files: `workzone:reality-data:transform`
        - model files: `workzone:cad-model:transform`
        - geoImage files: `workzone:geoimages:transform`
        - video3d files: `workzone:video3d:transform`
      tags:
        - File
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/filesTransformationUpdate'
      responses:
        '200':
          description: |
            The files are successfully updated.

            The response returns the updated files information.
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/File'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/files/bulk-transformation" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '[
                {
                  "id": "...",
                  "position": {"x": 1., "y": 2., "z": 3.},
                  "rotation": {"x": 4., "y": 5., "z": 6., "w": 7.}
                }
              ]'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/files/bulk-transformation"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token},
              json = [
                {
                  "id": "...",
                  "position": {"x": 1., "y": 2., "z": 3.},
                  "rotation": {"x": 4., "y": 5., "z": 6., "w": 7.}
                }
              ]
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";
            const payload = [
              {
                id: "...",
                position: { x: 1, y: 2, z: 3 },
                rotation: { x: 4, y: 5, z: 6, w: 7 },
              },
            ];

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/files/bulk-transformation`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(payload),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/share-links:
    get:
      summary: List share links
      operationId: getShareLinks
      description: |
        <span class='tag beta'>BETA</span>List share links.

        Permissions:
        A link will only be listed if the user needs has either:
        - the `workzone:share-links:read` permission
        - the `workzone:own-share-links:read` permission if he is the creator of the link

        Project owner automatically gets the `workzone:share-links:read` permission on all the project's work zones.
        Roles with the "Share" permission grants the `workzone:own-share-links:read` permission.
      tags:
        - Share Link
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      responses:
        '200':
          description: List of the project's share links
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/ShareLink'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/share-links" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/share-links"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/share-links`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    post:
      summary: Create a share link
      operationId: createShareLink
      description: |
        <span class='tag beta'>BETA</span>Create a share link.

        <span class='block' style='background-color: PapayaWhip;'><span class='icon' style='color: red; font-size:20px;'>&#9888;</span> A share link with no password will be freely usable by anyone with its id</span>

        Permissions:
        - the user need to have the `workzone:share-links:write` or `workzone:own-share-links:write` permission

        Project owner automatically gets the `workzone:share-links:write` permission on all the project's work zones.
        Roles with the "Share" permission grants the `workzone:own-share-links:write` permission.
      tags:
        - Share Link
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/shareLinkCreate'
      responses:
        '201':
          description: The share link was created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShareLink'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/share-links" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                "name": "my new link",
                "workzoneId": "urn:cintoo:workzone:...",
                "membershipRequired": false,
                "password": "this is my very long passphrase",
                "expiresAt": "2027-02-24T12:24:42Z",
                "shareOptions": {
                  "workzones": true,
                  "notes": true,
                  "issues": true,
                  "measurements": true,
                  "crops": false,
                  "tags": false
                },
                "logoBlob": "dXJuOmNpbnRvbzpmaWxlOjg3NmE3MGJhLTNjNjItNGRkMS05YjUyLTc5YzdmOTJlMDVmYw\/967dfd9cba4f5b8d9d029e6dc2243a9e.png",
                "savedView": {}
              }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            workzoneUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/share-links"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "name": "my new link",
                "workzoneId": workzoneUUID,
                "membershipRequired": false,
                "password": "this is my very long passphrase",
                "expiresAt": "2027-02-24T12:24:42Z",
                "shareOptions": {
                  "workzones": true,
                  "notes": true,
                  "issues": true,
                  "measurements": true,
                  "crops": false,
                  "tags": false
                },
                "logoBlob": "dXJuOmNpbnRvbzpmaWxlOjg3NmE3MGJhLTNjNjItNGRkMS05YjUyLTc5YzdmOTJlMDVmYw\/967dfd9cba4f5b8d9d029e6dc2243a9e.png",
                "savedView": {}
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/share-links`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                    "name": "my new link",
                    "workzoneId": "urn:cintoo:workzone:...",
                    "membershipRequired": false,
                    "password": "this is my very long passphrase",
                    "expiresAt": "2027-02-24T12:24:42Z",
                    "shareOptions": {
                      "workzones": true,
                      "notes": true,
                      "issues": true,
                      "measurements": true,
                      "crops": false,
                      "tags": false
                    },
                    "logoBlob": "dXJuOmNpbnRvbzpmaWxlOjg3NmE3MGJhLTNjNjItNGRkMS05YjUyLTc5YzdmOTJlMDVmYw\/967dfd9cba4f5b8d9d029e6dc2243a9e.png",
                    "savedView": {}
                  }
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/share-links/{shareLinkRef}:
    get:
      summary: Get a share link
      operationId: getShareLink
      description: |
        <span class='tag beta'>BETA</span>Get details on a share link.

        Permissions:
        The user need to either:
        - have the `workzone:share-links:read` permission
        - have the `workzone:own-share-links:read` permission and be the creator of the link

        Project owner automatically gets the `workzone:share-links:read` permission on all the project's work zones.
        Roles with the "Share" permission grants the `workzone:own-share-links:read` permission.
      tags:
        - Share Link
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/shareLinkRef'
      responses:
        '200':
          description: Get a share link
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShareLink'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export SHARELINKID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/share-links/$SHARELINKID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            shareLinkID= "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/share-links/%s"
                % (host, accountUUID, projectUUID, shareLinkID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const shareLinkId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/share-links/${shareLinkId}`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
    delete:
      summary: Delete a share link
      operationId: deleteShareLink
      description: |
        <span class='tag beta'>BETA</span>Delete a share link.

        Permissions:
        The user need to either:
        - have the `workzone:share-links:write` permission
        - have the `workzone:own-share-links:write` permission and be the creator of the link

        Project owner automatically gets the `workzone:share-links:write` permission on all the project's work zones.
        Roles with the "Share" permission grants the `workzone:own-share-links:write` permission.
      tags:
        - Share Link
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/shareLinkRef'
      responses:
        '204':
          description: The share link was deleted successfully
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export SHARELINKID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/share-links/$SHARELINKID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            shareLinkID= "..."
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/projects/%s/share-links/%s"
                % (host, accountUUID, projectUUID, shareLinkID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const shareLinkId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/share-links/${shareLinkId}`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
    put:
      summary: Update a share link
      operationId: updateShareLink
      description: |
        <span class='tag beta'>BETA</span>Update a share link.

        <span class='block' style='background-color: PapayaWhip;'><span class='icon' style='color: red; font-size:20px;'>&#9888;</span> Overwrites all fields. Missing fields will be considered as null and current values will be erased</span>

        Note: the `password` field has a special semantic here, to allow updating a share link without supplying the password every time.
        It can be either:
        - null or absent field: the password will remain the same.
        - "" (an empty string): the password will be removed.
        - "..." (a non-empty string): the password will be replaced.

        <span class='block' style='background-color: PapayaWhip;'><span class='icon' style='color: red; font-size:20px;'>&#9888;</span> A share link with no password will be freely usable by anyone with its id</span>

        Permissions:

        The user need to either:
        - have the `workzone:share-links:write` permission
        - have the `workzone:own-share-links:write` permission and be the creator of the link

        Project owner automatically gets the `workzone:share-links:write` permission on all the project's workzones.
        Roles with the "Share" permission grants the `workzone:own-share-links:write` permission.
      tags:
        - Share Link
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/shareLinkRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/shareLinkUpdate'
      responses:
        '200':
          description: The share link was updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ShareLink'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export SHARELINKID=...

            curl --request PUT --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/share-links/$SHARELINKID" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                "name": "my new link",
                "membershipRequired": false,
                "password": "this is my new very long passphrase",
                "expiresAt": "2027-02-24T12:24:42Z",
                "shareOptions": {
                  "workzones": true,
                  "notes": true,
                  "issues": true,
                  "measurements": true,
                  "crops": false,
                  "tags": false
                },
                "logoBlob": "dXJuOmNpbnRvbzpmaWxlOjg3NmE3MGJhLTNjNjItNGRkMS05YjUyLTc5YzdmOTJlMDVmYw\/967dfd9cba4f5b8d9d029e6dc2243a9e.png",
                "savedView": {}
              }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            workzoneUUID = "..."
            shareLinkID= "..."
            token = "..."

            response = requests.put(
              "https://%s/api/2/accounts/%s/projects/%s/share-links/%s"
                % (host, accountUUID, projectUUID, shareLinkID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "name": "my new link",
                "membershipRequired": false,
                "password": "this is my new very long passphrase",
                "expiresAt": "2027-02-24T12:24:42Z",
                "shareOptions": {
                  "workzones": true,
                  "notes": true,
                  "issues": true,
                  "measurements": true,
                  "crops": false,
                  "tags": false
                },
                "logoBlob": "dXJuOmNpbnRvbzpmaWxlOjg3NmE3MGJhLTNjNjItNGRkMS05YjUyLTc5YzdmOTJlMDVmYw\/967dfd9cba4f5b8d9d029e6dc2243a9e.png",
                "savedView": {}
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const shareLinkId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/share-links/${shareLinkId}`;
            const res = await fetch(url, {
              method: "PUT",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                    "name": "my new link",
                    "membershipRequired": false,
                    "password": "this is my new very long passphrase",
                    "expiresAt": "2027-02-24T12:24:42Z",
                    "shareOptions": {
                      "workzones": true,
                      "notes": true,
                      "issues": true,
                      "measurements": true,
                      "crops": false,
                      "tags": false
                    },
                    "logoBlob": "dXJuOmNpbnRvbzpmaWxlOjg3NmE3MGJhLTNjNjItNGRkMS05YjUyLTc5YzdmOTJlMDVmYw\/967dfd9cba4f5b8d9d029e6dc2243a9e.png",
                    "savedView": {}
                  }
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/blobs/{blobRef}:
    get:
      summary: Download a blob
      operationId: getBlob
      description: Download a blob, via a redirection to pre-signed URL
      tags:
        - File
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - name: blobRef
          in: path
          required: true
          schema:
            type: string
            description: a blob ref -> `blob` attribute in File schema
            example: 1f74af182236bd4e5df2c3f18f3d94fe.png
      responses:
        '302':
          description: Redirection to a pre-signed download link, in the Location header
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export BLOBID=...

            curl --location "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/blobs/$BLOBID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            blobId = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/blobs/%s"
                % (host, accountUUID, projectUUID, blobId),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            with open(blobId, mode='wb') as localfile:
              localfile.write(response.content)
        - lang: TypeScript
          source: |
            import { writeFile } from "node:fs/promises";


              const host = "aec.cintoo.com";
              const accountUUID = "...";
              const projectUUID = "...";
              const blobId = "...";
              const token = "...";

              const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/blobs/${blobId}`;
              const res = await fetch(url, {
                redirect: "follow",
                headers: {
                  "Authorization": `Bearer ${token}`,
                },
              });

              console.log(res.status);
              const fileBuffer = Buffer.from(await res.arrayBuffer());
              await writeFile(blobId, fileBuffer);
  /api/2/accounts/{accountRef}/projects/{projectRef}/blobs:
    post:
      summary: Get pre-signed urls for multiple blobs
      operationId: getBlobs
      description: Get pre-signed urls for multiple blobs.
      tags:
        - File
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              type: array
              items:
                type: string
                description: list of blob refs -> `blob` attribute in File schema
                example: 1f74af182236bd4e5df2c3f18f3d94fe.png
      responses:
        '200':
          description: Pre-signed urls to the requested blobs
          content:
            application/json:
              schema:
                type: object
                required:
                  - expiresIn
                  - urls
                properties:
                  expiresIn:
                    description: validity duration of urls, in seconds
                    type: integer
                  urls:
                    description: map associating each blob ref to its pre-signed download link
                    type: object
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export BLOBID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/blobs" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "[ \"$BLOBID\" ]"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            blobId = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/blobs"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = [ blobId ]
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";
            const blobId = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/blobs`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                [ blobId ]
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/workzones/{workzoneRef}/bulk-files:
    post:
      summary: Get a signed URL for cloud file upload
      operationId: signUploadURL
      description: |
        <span class='tag beta'>BETA</span>Get a signed URL for cloud file upload

        Requires the permission `workzone:documents:write` and/or `workzone:reality-data:write` on the workzone, depending on the file types

        After this call, use the `url`s from the response to upload the files.
        Note that there are examples on how to upload the data after calling the endpoints in the example section.

        Required headers to push the files
        * `Content-MD5: xxx` with `xxx` being a base 64 encode of the md5sum in binary format.\
          Example: `MD5SUM="$(openssl md5 -binary < "$FILENAME" | base64)"`
        * `Cache-Control: max-age=2592000`
        * `x-ms-blob-type: BlockBlob` mandatory if the storage type is Azure. \
          Note: always adding the `x-ms-blob-type` could be a good idea as Aws ignores it anyway.
      tags:
        - File
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/workzoneRef'
      requestBody:
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/bulk-files'
      responses:
        '200':
          description: The signing process succeeded
          content:
            application/json:
              schema:
                type: object
                properties:
                  provider:
                    type: string
                  expiresIn:
                    type: integer
                  files:
                    type: array
                    items:
                      type: object
                      properties:
                        name:
                          type: string
                        extension:
                          type: string
                        size:
                          type: integer
                        md5:
                          type: string
                        url:
                          type: string
                        blobName:
                          $ref: '#/components/schemas/BlobName'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTID=...
            export PROJECTID=...
            export WORKZONEID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTID/projects/$PROJECTID/workzones/$WORKZONEID/bulk-files" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '[
                {
                  "name":"tumbleweed.gif",
                  "extension":"gif",
                  "size":448459,
                  "md5":"da04845eaedb7db7f5b6f32c4076821f",
                  "isAttachment": false
                }
              ]'

            # -- UPLOAD OF THE FILE --

            # the url received in the body of the response
            export URL=...
            export FILENAME=...

            MD5SUM="$(openssl md5 -binary < "$FILENAME" | base64)"
            curl --request PUT --url "$URL" --header "Content-Type: application/json" -T "$FILENAME" -H "Content-MD5: ${MD5SUM}" -H "Cache-Control: max-age=2592000" -H "x-ms-blob-type: BlockBlob" -i
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountID = "..."
            projectID = "..."
            workzoneID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/workzones/%s/bulk-files"
                % (host, accountID, projectID, workzoneID),
              headers = { "Authorization": "Bearer %s" % token },
              json = [
                {
                  "name":"tumbleweed.gif",
                  "extension":"gif",
                  "size":448459,
                  "md5":"da04845eaedb7db7f5b6f32c4076821f",
                  "isAttachment": false
                }
              ]
            )
            print(response.status_code)
            print(response.json())

            import base64
            import hashlib

            # the url received in the body of the response
            export url=...
            export filename=...

            hash_md5 = hashlib.md5()
            with open(filename, "rb") as fin:
                for chunk in iter(lambda: fin.read(4096), b""):
                    hash_md5.update(chunk)
            digest = hash_md5.digest()
            md5 = base64.b64encode(digest).decode("utf-8")

            headers = {
              "Content-MD5": md5,
              "Cache-Control": "max-age=2592000",
              "x-ms-blob-type": "BlockBlob", # mandatory if storage type is azure
            }
            data = open(filename, "rb")
            r = requests.put(url, data=data, headers=headers)
            print(r.status_code)
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountId = "...";
            const projectId = "...";
            const workzoneId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountId}/projects/${projectId}/workzones/${workzoneId}/bulk-files`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                [
                    {
                      "name":"tumbleweed.gif",
                      "extension":"gif",
                      "size":448459,
                      "md5":"da04845eaedb7db7f5b6f32c4076821f",
                      "isAttachment": false
                    }
                  ]
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    put:
      summary: Register cloud files on a workzone
      operationId: registerFiles
      description: |
        <span class='tag beta'>BETA</span>Register cloud files on a workzone

        Requires the permission `workzone:documents:write` and/or `workzone:reality-data:write` in the work zone, depending on the file types

        This actually adds an already uploaded file in a work zone, so that it appears in the file browser
      tags:
        - File
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/workzoneRef'
      requestBody:
        content:
          application/json:
            schema:
              type: array
              items:
                $ref: '#/components/schemas/bulk-files'
      responses:
        '200':
          description: the files have been correctly registered
          content:
            application/json:
              schema:
                type: array
                items:
                  type: object
                  properties:
                    name:
                      type: string
                    extension:
                      type: string
                    size:
                      type: integer
                    md5:
                      type: string
                    id:
                      $ref: '#/components/schemas/FileUrn'
                    blobName:
                      $ref: '#/components/schemas/BlobName'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTID=...
            export PROJECTID=...
            export WORKZONEID=...

            curl --request PUT --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTID/projects/$PROJECTID/workzones/$WORKZONEID/bulk-files" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '[
                {
                  "name":"tumbleweed.gif",
                  "extension":"gif",
                  "size":448459,
                  "md5":"da04845eaedb7db7f5b6f32c4076821f",
                  "isAttachment": false
                }
              ]'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountID = "..."
            projectID = "..."
            workzoneID = "..."
            token = "..."

            response = requests.put(
              "https://%s/api/2/accounts/%s/projects/%s/workzones/%s/bulk-files"
                % (host, accountID, projectID, workzoneID),
              headers = { "Authorization": "Bearer %s" % token },
              json = [
                {
                  "name":"tumbleweed.gif",
                  "extension":"gif",
                  "size":448459,
                  "md5":"da04845eaedb7db7f5b6f32c4076821f",
                  "isAttachment": false
                }
              ]
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountId = "...";
            const projectId = "...";
            const workzoneId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountId}/projects/${projectId}/workzones/${workzoneId}/bulk-files`;
            const res = await fetch(url, {
              method: "PUT",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                [
                    {
                      "name":"tumbleweed.gif",
                      "extension":"gif",
                      "size":448459,
                      "md5":"da04845eaedb7db7f5b6f32c4076821f",
                      "isAttachment": false
                    }
                  ]
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/workzones/{workzoneRef}/copy:
    post:
      summary: Copy a work zone
      operationId: copyWorkzone
      description: |
        <span class='tag beta'>BETA</span>Copy a work zone, its sub-work zones, and the files on these work zones.

        The destination of the copy is provided by:
        - `destinationProject`: if not provided or null, a new project is created, and the copied work zone will
          be the root work zone of the new project.
        - `destinationWorkzone`: if provided and not null, specify the work zone inside the destination project in which
          the copied work zone will be. Default is the root work zone of the destination project.
          Cannot be provided if `destinationProject` is not provided.
        - Only in case a new project is created (`destinationProject` not provided), the `destinationSubscription`
          can be provided to assign the new project to this subscription. Default is to use the same subscription as
          the source project.

        Notes:
        - the resources like tags, annotations, crops, etc... are not copied. Only the structure and the files
          are copied.
        - if the request is accepted, the server responds immediately with a 202 status code, but at this moment
          the copy is not yet processed. It will be processed asynchronously, and the user will receive a
          notification by email once the process is finished (either successfully or with an error).
        - if a copy is already in progress on the source or destination project, it will fail

        Permissions:
        - On the source work zone, the user must be contributor and have the permissions `workzone:workzones:read`,
          `workzone:reality-data:read` and `workzone:cad-model:read`
          ("View Reality Data" and "Upload and download documents"),
           which are automatically included for the project owner.
        - If the destination is a new project, the user must be a project manager.
        - If the destination is an existing project, the user must be a contributor of the destination work zone
          with permissions `workzone:workzones:write`, `workzone:reality-data:write` and `workzone:cad-model:write`
          ("Work Zones", "Upload or Delete Reality Data", "Upload and download documents"),
           which are automatically included for the project owner.

        <span class='block' style='background-color: PapayaWhip;'><span class='icon' style='color: red; font-size:20px;'>&#9888;</span> We strongly recommend not to make any modification in the source work zone (and its sub-work zones) while the copy is in progress. If any changes are done in the source, they may not be taken into account and it might compromise the result. Both source and destination projects may be temporarily not accessible during the copy.</span>
      tags:
        - Workzone
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/workzoneRef'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
                - destinationName
              properties:
                destinationName:
                  type: string
                  minLength: 1
                  maxLength: 255
                destinationProject:
                  description: The project to copy into. If not provided, a new project is created.
                  allOf:
                    - $ref: '#/components/schemas/ProjectRef'
                  type: string
                  nullable: true
                destinationWorkzone:
                  description: The work zone to copy into. Only valid if a destination project is specified.
                  allOf:
                    - $ref: '#/components/schemas/WorkzoneRef'
                  type: string
                  nullable: true
                destinationSubscription:
                  description: The subscription to use for the new project. Only valid if a destination project is not specified.
                  allOf:
                    - $ref: '#/components/schemas/SubscriptionRef'
                  type: string
                  nullable: true
      responses:
        '202':
          description: The copy has been accepted and will be processed in a short time
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTID=...
            export PROJECTID=...
            export WORKZONEID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTID/projects/$PROJECTID/workzones/$WORKZONEID/copy" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{ "destinationName": "My copy" }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountID = "..."
            projectID = "..."
            workzoneID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/workzones/%s/copy"
                % (host, accountID, projectID, workzoneID),
              headers = { "Authorization": "Bearer %s" % token },
              json = { "destinationName": "My copy" }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountId = "...";
            const projectId = "...";
            const workzoneId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountId}/projects/${projectId}/workzones/${workzoneId}/copy`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                { "destinationName": "My copy" }
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/annotations:
    get:
      summary: List annotations in a project
      operationId: getAnnotationsFromProject
      description: |
        <span class='tag beta'>BETA</span>List all annotations in a project

        Only annotations on work zones where the user has the permission `workzone:annotations:read` are returned.
      tags:
        - Annotation
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/includeComments'
        - $ref: '#/components/parameters/includeAttachments'
      responses:
        '200':
          description: List of annotations
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Annotation'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/annotations" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/annotations"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/annotations`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    post:
      summary: Create an annotation
      operationId: createAnnotation
      description: |
        <span class='tag beta'>BETA</span>Create an annotation

        Requires the permission `workzone:annotations:write` on the work zone.

        A maximum of 3000 annotations are allowed on the same project. If this limit is reached, an error 400 with
        error code `project-max-annotations` is returned.
      tags:
        - Annotation
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/annotationCreate'
      responses:
        '201':
          description: The annotation was created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Annotation'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export WORKZONEID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/annotations" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "{\"workzoneId\":\"$WORKZONEID\",\"type\":\"Note\",\"title\":\"test\",\"description\":\"test\",\"position\":\"1,2,3\"}"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            workzoneId = ""
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/annotations"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "workzoneId": workzoneId,
                "type": "Note",
                "title": "test",
                "description": "test",
                "position": "1,2,3"
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";
            const workzoneId = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/annotations`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {"workzoneId":workzoneId,"type":"Note","title":"test","description":"test","position":"1,2,3"}
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/annotations/{annotationRef}:
    get:
      summary: Get annotation
      operationId: getAnnotationById
      description: |
        <span class='tag beta'>BETA</span>Retrieve an annotation, optionally with comments and attachments

        Requires the permission `workzone:annotations:read` on the work zone where the annotation is.
      tags:
        - Annotation
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/annotationRef'
        - $ref: '#/components/parameters/includeComments'
        - $ref: '#/components/parameters/includeAttachments'
      responses:
        '200':
          description: The content of the annotation
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Annotation'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export ANNOTATIONID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/annotations/$ANNOTATIONID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            annotationId = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/annotations/%s"
                % (host, accountUUID, projectUUID, annotationId),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const annotationId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/annotations/${annotationId}`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    put:
      summary: Update an annotation
      operationId: updateAnnotation
      description: |
        <span class='tag beta'>BETA</span>Update an annotation

        <span class='block' style='background-color: PapayaWhip;'><span class='icon' style='color: red; font-size:20px;'>&#9888;</span> Overwrites all fields. Missing fields will be considered as null and current values will be erased</span>

        Requires the permission `workzone:annotations:write` on the work zone.
      tags:
        - Annotation
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/annotationRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/annotationUpdate'
      responses:
        '200':
          description: The annotation was updated successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Annotation'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export ANNOTATIONUUID=...

            curl --request PUT --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/annotations/$ANNOTATIONUUID" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "{\"type\":\"Note\",\"title\":\"test\",\"description\":\"test\",\"position\":\"1,2,3\"}"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            annotationUUID = ""
            token = "..."

            response = requests.put(
              "https://%s/api/2/accounts/%s/projects/%s/annotations/%s"
                % (host, accountUUID, projectUUID, annotationUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "type": "Note",
                "title": "test",
                "description": "test",
                "position": "1,2,3"
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const annotationUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/annotations/${annotationUUID}`;
            const res = await fetch(url, {
              method: "PUT",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {"type":"Note","title":"test","description":"test","position":"1,2,3"}
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    delete:
      summary: Delete an annotation
      operationId: deleteAnnotation
      description: |
        <span class='tag beta'>BETA</span>Delete an annotation

        Requires the permission `workzone:annotations:write` on the work zone.
      tags:
        - Annotation
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/annotationRef'
      responses:
        '204':
          description: The annotation was deleted successfully
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export ANNOTATIONUUID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/annotations/$ANNOTATIONUUID" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            annotationUUID = ""
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/projects/%s/annotations/%s"
                % (host, accountUUID, projectUUID, annotationUUID),
              headers = { "Authorization": "Bearer %s" % token }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const annotationUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/annotations/${annotationUUID}`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/accounts/{accountRef}/projects/{projectRef}/annotations/{annotationRef}/comments:
    post:
      summary: Create a comment on an annotation
      operationId: createAnnotationComment
      description: |
        <span class='tag beta'>BETA</span>Create a comment on an annotation

        Requires the permission `workzone:annotations:write` on the work zone.
      tags:
        - Annotation
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/annotationRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/annotationCommentCreate'
      responses:
        '201':
          description: The comment was created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnnotationComment'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export ANNOTATIONUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/annotations/$ANNOTATIONUUID/comments" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "{\"description\":\"my comment\"}"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            annotationUUID = ""
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/annotations/%s/comments"
                % (host, accountUUID, projectUUID, annotationUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "description": "my comment"
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const annotationUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/annotations/${annotationUUID}/comments`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {"description":"my comment"}
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/annotations/bulk-create-integration:
    post:
      summary: Create integration links on annotations
      operationId: createAnnotationIntegrationLinks
      description: |
        <span class='tag beta'>BETA</span>Create integration links on annotations

        Requires the permission `project:annotations:create-integration-link` on the project, which is automatically
        granted for users having the permission `workzone:annotations:write` on the root work zone.

        Pre-requisites: to be created, an integration link needs the project to have a valid configuration:
        * An active subscription with BCM enabled
        * For Autodesk:
          * the project must be configured and linked to an Autodesk hub and project
          * a valid Autodesk token must exist for the user who created the annotation (logged in)
        * For Procore:
          * the project must be configured and linked to a Procore company and project
          * a valid Procore token must exist for the user who created the annotation (logged in)
        * For Newforma Konekt:
          * A valid Newforma Konekt token must exist on the account
          * The project must be configured and linked to a Konekt hub and project
      tags:
        - Integrations
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/annotationCreateIntegrationLinks'
      responses:
        '202':
          description: |
            Returns the list of annotations' URN for which links have been initialized, and for each annotation the types of
            integration that have been initialized.

            If an annotation is not returned, or a requested integration type is not returned for an annotation,
            it means one of those situations:
             * the link already exist
             * the link cannot be created because the configuration is incorrect or missing

            After this call, the links are initialized but the creation will be processed by an asynchronous batch.
            To check the status, you can get the annotations, and check the `integrations` field. In this field,
            an integration with inner field `url` set to `null` means it is still pending.
            Once the `url` field is not null, it means the link has been successfully created.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AnnotationBulkCreateIntegrationLinksResponse'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export ANNOTATIONUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/annotations/bulk-create-integration" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "{\"annotations\":[\"$ANNOTATIONUUID\"], \"integrationTypes\":[\"AUTODESK\",\"PROCORE\"]}"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            annotationUUID = ""
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/annotations/bulk-create-integration"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "annotations": [annotationUUID],
                "integrationTypes": ["AUTODESK","PROCORE"]
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";
            const annotationUUID = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/annotations/bulk-create-integration`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {"annotations":[annotationUUID], "integrationTypes":["AUTODESK","PROCORE"]}
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/integrations:
    get:
      summary: List integrations on the project
      operationId: getProjectIntegrations
      description: |
        <span class='tag beta'>BETA</span>List all integrations on the project

        Requires the permission `project:integrations:read` automatically granted for users having
        `workzone:annotations:write` on the root work zone of the project.
      tags:
        - Integrations
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      responses:
        '200':
          description: List of integrations
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectIntegrations'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/integrations" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/integrations"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/integrations`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/integrations/konekt:
    get:
      summary: Get configuration of integration with Konekt on a Project
      operationId: getKonektProjectIntegration
      description: |
        <span class='tag beta'>BETA</span>Get the integration with Newforma Konekt configured on the project

        Requires the permission `project:integrations:read` automatically granted for users having
        `workzone:annotations:write` on the root work zone of the project.
      tags:
        - Konekt
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      responses:
        '200':
          description: The configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectIntegrationKonekt'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/integrations/konekt" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/integrations/konekt"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/integrations/konekt`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    post:
      summary: Create configuration of integration with Konekt on Project
      operationId: createKonektProjectIntegration
      description: |
        <span class='tag beta'>BETA</span>Create the configuration for the integration with Newforma Konekt.

        Requires the permission `project:integrations:write` automatically granted for users having
        `workzone:annotations:write` on the root work zone of the project.
      tags:
        - Konekt
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/projectIntegrationKonektCreate'
      responses:
        '201':
          description: The integration was successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectIntegrationKonekt'
        '409':
          description: A configuration already exists on this project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorConflict'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/integrations/konekt" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "{\"hubId\":\"...\",\"konektProjectId\":1,\"options\":{\"allowInviteUsers\":false,\"syncStrategy\":\"BOTH_WAY\"}}"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/integrations/konekt"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "hubId": "...",
                "konektProjectId": 1,
                "options": {"allowInviteUsers": False, "syncStrategy": "BOTH_WAY"}
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/integrations/konekt`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {"hubId":"...","konektProjectId":1,"options":{"allowInviteUsers":false,"syncStrategy":"BOTH_WAY"}}
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    put:
      summary: Update configuration of the integration with Konekt on Project
      operationId: updateKonektProjectIntegration
      description: |
        <span class='tag beta'>BETA</span>Update the configuration for the integration with Newforma Konekt.

        Requires the permission `project:integrations:write` automatically granted for users having
        `workzone:annotations:write` on the root work zone of the project.
      tags:
        - Konekt
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/projectIntegrationKonektUpdate'
      responses:
        '200':
          description: The integration was successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectIntegrationKonekt'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request PUT --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/integrations/konekt" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "{\"hubId\":\"...\",\"konektProjectId\":1,\"options\":{\"allowInviteUsers\":false,\"syncStrategy\":\"BOTH_WAY\"}}"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.put(
              "https://%s/api/2/accounts/%s/projects/%s/integrations/konekt"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "hubId": "...",
                "konektProjectId": 1,
                "options": {"allowInviteUsers": False, "syncStrategy": "BOTH_WAY"}
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/integrations/konekt`;
            const res = await fetch(url, {
              method: "PUT",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {"hubId":"...","konektProjectId":1,"options":{"allowInviteUsers":false,"syncStrategy":"BOTH_WAY"}}
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    delete:
      summary: Delete the integration with Konekt on Project
      operationId: deleteKonektProjectIntegration
      description: |
        <span class='tag beta'>BETA</span>Delete the integration with Newforma Konekt on the project.

        Requires the permission `project:integrations:write` automatically granted for users having
        `workzone:annotations:write` on the root work zone of the project.
      tags:
        - Konekt
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      responses:
        '204':
          description: The integration was successfully deleted
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/integrations/konekt" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/projects/%s/integrations/konekt"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token }
            )
            print(response.status_code)
            if response.status_code != 204:
              print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/integrations/konekt`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/accounts/{accountRef}/projects/{projectRef}/integrations/autodesk:
    get:
      summary: Get configuration of integration with Autodesk on a Project
      operationId: getAutodeskProjectIntegration
      description: |
        <span class='tag beta'>BETA</span>Get the integration with Autodesk configured on the project

        Requires the permission `project:integrations:read` automatically granted for users having
        `workzone:annotations:write` on the root work zone of the project.
      tags:
        - Autodesk
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      responses:
        '200':
          description: The configuration
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectIntegrationAutodesk'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/integrations/autodesk" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/integrations/autodesk"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/integrations/autodesk`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    post:
      summary: Create configuration of integration with Autodesk on Project
      operationId: createAutodeskProjectIntegration
      description: |
        <span class='tag beta'>BETA</span>Create the configuration for the integration with Autodesk.

        Requires the permission `project:integrations:write` automatically granted for users having
        `workzone:annotations:write` on the root work zone of the project.
      tags:
        - Autodesk
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/projectIntegrationAutodeskCreate'
      responses:
        '201':
          description: The integration was successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectIntegrationAutodesk'
        '409':
          description: A configuration already exists on this project
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ErrorConflict'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/integrations/autodesk" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "{\"autodeskHubId\":\"...\",\"autodeskProjectId\":\"...\"}"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/integrations/autodesk"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "autodeskHubId": "...",
                "autodeskProjectId": "..."
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/integrations/autodesk`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {"autodeskHubId":"...","autodeskProjectId":"..."}
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    put:
      summary: Update configuration of the integration with Autodesk on Project
      operationId: updateAutodeskProjectIntegration
      description: |
        <span class='tag beta'>BETA</span>Update the configuration for the integration with Autodesk.

        Requires the permission `project:integrations:write` automatically granted for users having
        `workzone:annotations:write` on the root work zone of the project.
      tags:
        - Autodesk
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/projectIntegrationAutodeskUpdate'
      responses:
        '200':
          description: The integration was successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ProjectIntegrationAutodesk'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request PUT --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/integrations/autodesk" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "{\"autodeskHubId\":\"...\",\"autodeskProjectId\":\"...\"}"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.put(
              "https://%s/api/2/accounts/%s/projects/%s/integrations/autodesk"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "autodeskHubId": "...",
                "autodeskProjectId": "..."
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/integrations/autodesk`;
            const res = await fetch(url, {
              method: "PUT",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {"autodeskHubId":"...","autodeskProjectId":"..."}
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    delete:
      summary: Delete the integration with Autodesk on Project
      operationId: deleteAutodeskProjectIntegration
      description: |
        <span class='tag beta'>BETA</span>Delete the integration with Autodesk on the project.

        Requires the permission `project:integrations:write` automatically granted for users having
        `workzone:annotations:write` on the root work zone of the project.
      tags:
        - Autodesk
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      responses:
        '204':
          description: The integration was successfully deleted
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/integrations/autodesk" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/projects/%s/integrations/autodesk"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token }
            )
            print(response.status_code)
            if response.status_code != 204:
              print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/integrations/autodesk`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/accounts/{accountRef}/projects/{projectRef}/crops:
    get:
      summary: Get Crops
      operationId: getCrops
      description: |
        <span class='tag beta'>BETA</span>Get all crops related to a project.

        Requires the permission `workzone:crops:read` on at least one work zone of the project.
        The crop returned are all crops on work zones the user has this permission.
      tags:
        - Crop
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/workzonesFilter'
      responses:
        '200':
          description: Content of each Crop inside the project you have access to
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Crop'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/crops" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/crops"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/crops`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    post:
      summary: Create a crop
      operationId: createCrop
      description: |
        <span class='tag beta'>BETA</span>Create a crop

        Requires the permission `workzone:crops:write` on the work zone.
      tags:
        - Crop
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/cropCreate'
      responses:
        '201':
          description: The crop was created successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Crop'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export WORKZONEUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/crops" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "{
                \"workzoneId\":\"$WORKZONEUUID\",
                \"crop\":\"CropContent\",
                \"actualCrop\":\"actualCropContent\",
                \"rotation\":\"rotationContent\",
                \"cameraType\":\"scan\",
                \"cameraState\":\"cameraStateContent\",
                \"scan\":\"scanContent\",
                \"name\":\"cropName\"
              }"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            workzoneUUID = ""
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/crops"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "workzoneId": workzoneUUID,
                "crop": "CropContent",
                "actualCrop": "actualCropContent",
                "rotation": "rotationContent",
                "cameraType": "scan",
                "cameraState": "cameraStateContent",
                "scan": "scanContent",
                "name": "cropName"
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const workzoneId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/crops`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify({
                workzoneId,
                crop: "CropContent",
                actualCrop: "actualCropContent",
                rotation: "rotationContent",
                cameraType: "scan",
                cameraState: "cameraStateContent",
                scan: "scanContent",
                name: "cropName",
              }),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/crops/{cropRef}:
    get:
      summary: Get Crop
      operationId: getCrop
      description: |
        <span class='tag beta'>BETA</span>Get details about a specific crop, related to a project.

        Requires the permission `workzone:crops:read` on the work zone in which the crop has been created.
      tags:
        - Crop
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/cropRef'
      responses:
        '200':
          description: Content of Crop
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Crop'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export CROPUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/crops/$CROPUUID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            cropUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/crops/%s"
                % (host, accountUUID, projectUUID, cropUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const cropUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/crops/${cropUUID}`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    put:
      summary: Update Crop
      operationId: putCrop
      description: |
        <span class='tag beta'>BETA</span>Update a Crop. This can also be a work zone move by providing new workzoneId in which you have required permission.
        Note that missing fields, even optionals, are considered as null (useful for removing fields).

        Requires the permission `workzone:crops:write` in the work zone in which the crop belong to, and in case of crop move, also in the target work zone.
      tags:
        - Crop
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/cropRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/cropUpdate'
      responses:
        '200':
          description: Details of updated crop
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Crop'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export CROPUUID=...
            export WORKZONEUUID=...

            curl --request PUT --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/crops/$CROPUUID" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "{
                \"workzoneId\":\"$WORKZONEUUID\",
                \"crop\":\"CropContent\",
                \"actualCrop\":\"actualCropContent\",
                \"rotation\":\"rotationContent\",
                \"cameraType\":\"scan\",
                \"cameraState\":\"cameraStateContent\",
                \"scan\":\"scanContent\",
                \"name\":\"cropName\"
              }"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            cropUUID = "..."
            workzoneUUID = ""
            token = "..."

            response = requests.put(
              "https://%s/api/2/accounts/%s/projects/%s/crops/%s"
                % (host, accountUUID, projectUUID, cropUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "workzoneId": workzoneUUID,
                "crop": "CropContent",
                "actualCrop": "actualCropContent",
                "rotation": "rotationContent",
                "cameraType": "scan",
                "cameraState": "cameraStateContent",
                "scan": "scanContent",
                "name": "cropName"
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const cropUUID = "...";
            const workzoneUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/crops/${cropUUID}`;
            const res = await fetch(url, {
              method: "PUT",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify({
                workzoneId: workzoneUUID,
                crop: "CropContent",
                actualCrop: "actualCropContent",
                rotation: "rotationContent",
                cameraType: "scan",
                cameraState: "cameraStateContent",
                scan: "scanContent",
                name: "cropName",
              }),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    delete:
      summary: Delete Crop
      operationId: deleteCrop
      description: |
        <span class='tag beta'>BETA</span>Delete a given Crop.

        Requires the permission `workzone:crops:write` in the work zone in which the crop belongs to.
      tags:
        - Crop
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/cropRef'
      responses:
        '204':
          description: The Crop has been successfully deleted
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export CROPUUID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/crops/$CROPUUID" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            cropUUID = "..."
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/projects/%s/crops/%s"
                % (host, accountUUID, projectUUID, cropUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const cropUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/crops/${cropUUID}`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/accounts/{accountRef}/projects/{projectRef}/crops/{cropRef}/cover:
    post:
      summary: Get a signed URL for crop cover file upload
      operationId: signCropCoverURL
      description: |
        <span class='tag beta'>BETA</span>- Get a signed URL for crop cover file upload

        Requires the permission `workzone:crops:write`.

        After this call, use the `url`s from the response to upload the crop cover file.

        Required headers to push the files
        * `Content-MD5: xxx` with `xxx` being a base 64 encode of the md5sum in binary format.\
          Example: `MD5SUM="$(openssl md5 -binary < "$FILENAME" | base64)"`
        * `Cache-Control: max-age=2592000`
        * `x-ms-blob-type: BlockBlob` mandatory if the storage type is Azure. \
          Note: always adding the `x-ms-blob-type` could be a good idea as Aws ignores it anyway.
      tags:
        - Crop
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/cropRef'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
                - name
                - extension
                - size
                - md5
              properties:
                name:
                  type: string
                extension:
                  type: string
                  enum:
                    - png
                    - jpg
                    - jpeg
                size:
                  type: integer
                  description: Size of the file (bytes)
                  minimum: 1
                  maximum: 10485760
                md5:
                  type: string
                  description: The md5 sum-check of the file
      responses:
        '200':
          description: The signing process succeeded
          content:
            application/json:
              schema:
                type: object
                required:
                  - provider
                  - expiresIn
                  - file
                properties:
                  provider:
                    type: string
                  expiresIn:
                    type: integer
                    description: After this amount of seconds, the upload link won't be available anymore
                  file:
                    type: object
                    required:
                      - name
                      - extension
                      - size
                      - md5
                      - url
                    properties:
                      name:
                        type: string
                      extension:
                        type: string
                      size:
                        type: integer
                        description: Size of the file (bytes)
                      md5:
                        type: string
                        description: The md5 sum-check of the file
                      url:
                        type: string
                        description: the url to which upload the file
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTID=...
            export PROJECTID=...
            export CROPID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTID/projects/$PROJECTID/crops/$CROPID/cover" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                  "name":"cover.png",
                  "extension":"png",
                  "size":448459,
                  "md5":"da04845eaedb7db7f5b6f32c4076821f"
                }'

            # -- UPLOAD OF THE FILE --

            # the url received in the body of the response
            export URL=...
            export FILENAME=...

            MD5SUM="$(openssl md5 -binary < "$FILENAME" | base64)"
            curl --request PUT --url "$URL" --header "Content-Type: application/json" -T "$FILENAME" -H "Content-MD5: ${MD5SUM}" -H "Cache-Control: max-age=2592000" -H "x-ms-blob-type: BlockBlob" -i
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountID = "..."
            projectID = "..."
            cropID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/crops/%s/cover"
                % (host, accountID, projectID, cropID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                  "name":"tumbleweed.png",
                  "extension":"png",
                  "size":448459,
                  "md5":"da04845eaedb7db7f5b6f32c4076821f"
                }
            )
            print(response.status_code)
            print(response.json())

            import base64
            import hashlib

            # the url received in the body of the response
            export url=...
            export filename=...

            hash_md5 = hashlib.md5()
            with open(filename, "rb") as fin:
                for chunk in iter(lambda: fin.read(4096), b""):
                    hash_md5.update(chunk)
            digest = hash_md5.digest()
            md5 = base64.b64encode(digest).decode("utf-8")

            headers = {
              "Content-MD5": md5,
              "Cache-Control": "max-age=2592000",
              "x-ms-blob-type": "BlockBlob", # mandatory if storage type is azure
            }
            data = open(filename, "rb")
            r = requests.put(url, data=data, headers=headers)
            print(r.status_code)
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountId = "...";
            const projectId = "...";
            const cropId = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountId}/projects/${projectId}/crops/${cropId}/cover`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                      "name":"cover.png",
                      "extension":"png",
                      "size":448459,
                      "md5":"da04845eaedb7db7f5b6f32c4076821f",
                    }
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/measurements:
    get:
      summary: Get Measurements
      operationId: getMeasurements
      description: |
        <span class='tag beta'>BETA</span>Get all Measurements related to a project.

        Requires the permission `workzone:measurements:read` on at least one work zone of the project.
        The measurements returned are all measurements on work zones the user has this permission.
      tags:
        - Measurement
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      responses:
        '200':
          description: Content of each Measurement inside the project you have access to
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Measurement'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/measurements" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/measurements"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/measurements`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/tag-lists:
    get:
      summary: Get tag lists
      operationId: getTagsOnProject
      description: |
        <span class='tag beta'>BETA</span>Get all tag lists in a project.

        Requires the permission `workzone:tags:read` on at least one work zone of the project.
        The tag lists returned are all tag lists on work zones the user has this permission.

        The project must have an active subscription.
      tags:
        - Tag List
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      responses:
        '200':
          description: List of tag lists
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/TagList'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/tag-lists" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/tag-lists"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/tag-lists`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    post:
      summary: Create a tag list
      operationId: createTagList
      description: |
        <span class='tag beta'>BETA</span>Create a tag list.

        Requires the permission `workzone:tags:write` on the work zone.

        The project must have an active subscription.
      tags:
        - Tag List
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/tagListCreate'
      responses:
        '201':
          description: The tag list was successfully created
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TagList'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/tag-lists" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                "workzoneId": "...",
                "tagListType": "3d",
                "name": "Test",
                "description": "This is a test",
                "color": "#123456",
                "metadataDefinition": [{"id": "test", "name": "label for test"}]
            }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/tag-lists"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "workzoneId": "...",
                "tagListType": "3d",
                "name": "Test",
                "description": "This is a test",
                "color": "#123456",
                "metadataDefinition": [{"id": "test", "name": "label for test"}]
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/tag-lists`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                    "workzoneId": "...",
                    "tagListType": "3d",
                    "name": "Test",
                    "description": "This is a test",
                    "color": "#123456",
                    "metadataDefinition": [{"id": "test", "name": "label for test"}]
                }
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/tag-lists/bulk-update:
    post:
      summary: Update multiple tag lists
      operationId: bulkUpdateTagLists
      description: |
        <span class='tag beta'>BETA</span>Update tag lists.

        Requires the permission `workzone:tags:write` on the work zones of the tag lists.

        The project must have an active subscription.
        The tag lists must not be pending.

        Note that the workzoneId may be updated to perform a move to another work zone. In this case the permission
        `workzone:tags:write` is also required on the target work zone.
      tags:
        - Tag List
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              type: array
              items:
                allOf:
                  - type: object
                    required:
                      - id
                    properties:
                      id:
                        $ref: '#/components/schemas/TagListRef'
                  - $ref: '#/components/schemas/tagListUpdate'
      responses:
        '200':
          description: The tag lists were successfully updated
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/TagList'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/tag-lists/bulk-update" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '[{
                "id": "...",
                "workzoneId": "...",
                "tagListType": "3d",
                "name": "Test",
                "description": "This is a test",
                "color": "#123456",
                "metadataDefinition": [{"id": "test", "name": "label for test"}]
            }]'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/tag-lists/bulk-update"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "id": "...",
                "workzoneId": "...",
                "tagListType": "3d",
                "name": "Test",
                "description": "This is a test",
                "color": "#123456",
                "metadataDefinition": [{"id": "test", "name": "label for test"}]
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/tag-lists/bulk-update`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                [{
                    "id": "...",
                    "workzoneId": "...",
                    "tagListType": "3d",
                    "name": "Test",
                    "description": "This is a test",
                    "color": "#123456",
                    "metadataDefinition": [{"id": "test", "name": "label for test"}]
                }]
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/tag-lists/bulk-delete:
    post:
      summary: Delete multiple tag lists
      operationId: bulkDeleteTagLists
      description: |
        <span class='tag beta'>BETA</span>Delete tag lists.

        The project must have an active subscription.
        The tag lists must not be pending.

        Requires the permission `workzone:tags:write` on the work zones of the tag lists.
      tags:
        - Tag List
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
                - ids
              properties:
                ids:
                  type: array
                  items:
                    $ref: '#/components/schemas/TagListRef'
      responses:
        '204':
          description: The tag lists were successfully deleted
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/tag-lists/bulk-delete" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{"ids": ["...", "..."]}'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/tag-lists/bulk-delete"
                % (host, accountUUID, projectUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "ids": ["...", "..."]
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/tag-lists/bulk-delete`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {"ids": ["...", "..."]}
              ),
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/accounts/{accountRef}/projects/{projectRef}/tag-lists/{tagListRef}:
    put:
      summary: Update a tag list
      operationId: updateTagList
      description: |
        <span class='tag beta'>BETA</span>Update a tag list.

        <span class='block' style='background-color: PapayaWhip;'><span class='icon' style='color: red; font-size:20px;'>&#9888;</span> Overwrites all fields. Missing fields will be considered as null and current values will be erased</span>

        Requires the permission `workzone:tags:write` on the work zone of the tag list.

        The project must have an active subscription.
        The tag list must not be pending.

        Note that the workzoneId may be updated to perform a move to another work zone. In this case the permission
        `workzone:tags:write` is also required on the target work zone.
      tags:
        - Tag List
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/tagListRef'
      requestBody:
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/tagListUpdate'
      responses:
        '200':
          description: The tag list was successfully updated
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TagList'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export LISTUUID=...

            curl --request PUT --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/tag-lists/$LISTUUID" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                "workzoneId": "...",
                "tagListType": "3d",
                "name": "Test",
                "description": "This is a test",
                "color": "#123456",
                "metadataDefinition": [{"id": "test", "name": "label for test"}]
            }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            listUUID = "..."
            token = "..."

            response = requests.put(
              "https://%s/api/2/accounts/%s/projects/%s/tag-lists/%s"
                % (host, accountUUID, projectUUID, listUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "workzoneId": "...",
                "tagListType": "3d",
                "name": "Test",
                "description": "This is a test",
                "color": "#123456",
                "metadataDefinition": [{"id": "test", "name": "label for test"}]
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const listUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/tag-lists/${listUUID}`;
            const res = await fetch(url, {
              method: "PUT",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                    "workzoneId": "...",
                    "tagListType": "3d",
                    "name": "Test",
                    "description": "This is a test",
                    "color": "#123456",
                    "metadataDefinition": [{"id": "test", "name": "label for test"}]
                }
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    delete:
      summary: Delete a tag list
      operationId: deleteTagList
      description: |
        <span class='tag beta'>BETA</span>Delete a tag list.

        The project must have an active subscription.
        The tag list must not be pending.

        Requires the permission `workzone:tags:write` on the work zone of the tag list.
      tags:
        - Tag List
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/tagListRef'
      responses:
        '204':
          description: The tag list was successfully deleted
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export LISTUUID=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/tag-lists/$LISTUUID" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            listUUID = ""
            token = "..."

            response = requests.delete(
              "https://%s/api/2/accounts/%s/projects/%s/tag-lists/%s"
                % (host, accountUUID, projectUUID, listUUID),
              headers = { "Authorization": "Bearer %s" % token }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const listUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/tag-lists/${listUUID}`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/tag-lists/{tagListRef}/ai-detection:
    post:
      summary: Object Detection on a tag list
      operationId: tagListAiDetection
      description: |
        <span class='tag beta'>BETA</span>Launch AI Detection on a tag list.

        Required:
        - the permission `workzone:tags:write` on the work zone of the tag list
        - the permissions `workzone:reality-data:read` and `workzone:export-jobs-reality-data:write`
          on the work zones of the scans

        The project must have an active subscription.
        The tag list must not be pending.

        After this call, the tag list will become *pending* until the AI detection is done, meaning no more
        operation can be performed on this tag list until the end of the detection.
      tags:
        - Tag List
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/tagListRef'
      requestBody:
        content:
          application/json:
            schema:
              type: object
              required:
                - classes
                - filesIds
              properties:
                classes:
                  type: array
                  items:
                    type: string
                  minLength: 1
                  description: The list of possible classes can be obtained using the [List AI detection classes](#tag/Tag-List/operation/listAIDetectionClasses) endpoint
                filesIds:
                  type: array
                  items:
                    $ref: '#/components/schemas/FileRef'
                  minLength: 1
      responses:
        '200':
          description: The AI detection has been successfully launched on the tag list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TagList'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export LISTUUID=...

            curl --request POST --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/tag-lists/$LISTUUID/ai-detection" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data '{
                "classes": ["control_box"],
                "filesIds": ["..."]
              }'
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            listUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/tag-lists/%s/ai-detection"
                % (host, accountUUID, projectUUID, listUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = {
                "classes": ["control_box"],
                "filesIds": ["..."]
              }
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const listUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/tag-lists/${listUUID}/ai-detection`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                {
                    "classes": ["control_box"],
                    "filesIds": ["..."]
                  }
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/tag-lists/{tagListRef}/tags:
    get:
      summary: Get tags
      operationId: getTagsOnList
      description: |
        <span class='tag beta'>BETA</span>Get tags from a tag list.

        Requires the permission `workzone:tags:read` on the work zone of the tag list.

        The project must have an active subscription.

        If the parameter `afterVersion` is not specified (or 0 or negative value), all tags of the tag list are returned.
        If it is specified, it will return all changes after the given version, including new tags, updated tags,
        but also deleted tags (with `isDeleted` set to true in the response).
      tags:
        - Tag
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/tagListRef'
        - $ref: '#/components/parameters/afterVersion'
      responses:
        '200':
          description: List of tags
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Tag'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export LISTUUID=...

            curl --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/tag-lists/$LISTUUID?afterVersion=0" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            listUUID = "..."
            token = "..."

            response = requests.get(
              "https://%s/api/2/accounts/%s/projects/%s/tag-lists/%s?afterVersion=0"
                % (host, accountUUID, projectUUID, listUUID),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const listUUID = "...";
            const token = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/tag-lists/${listUUID}?afterVersion=0`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/tag-lists/{tagListRef}/tags/bulk:
    post:
      summary: Bulk operation on tags
      operationId: bulkTags
      description: |
        <span class='tag beta'>BETA</span>Bulk operation on tags inside a tag list.

        Requires the permission `workzone:tags:write` on the work zone of the tag list.
        The tag list must not be pending, and must not exceed the maximum number of tags for a tag list (50000).

        The bulk operation perform all requests together, if any fails nothing is done (no partial execution).
      tags:
        - Tag
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/tagListRef'
      requestBody:
        description: |
          Operations to perform: tags to create, tags to update, and tags to delete.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/tagsBulkRequest'
      responses:
        '200':
          description: |
            All the operations are successful.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/tagsBulkResponse'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export TARGETTAGLISTUUID=...
            export SOURCETAGLISTUUID=...

            curl --request PUT --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/tag-lists/$TARGETTAGLISTUUID" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "[{\"sourceTagListId\":\"$SOURCETAGLISTUUID\",\"tagsIds\":[...]}"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            targetListUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/tag-lists/%s"
                % (host, accountUUID, projectUUID, targetListUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = [{
                "sourceTagListId": "...",
                "tagsIds": [...]
              }]
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const targetTagListUUID = "...";
            const token = "...";
            const sourceTagListUUID = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/tag-lists/${targetTagListUUID}`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                [{"sourceTagListId":sourceTagListUUID,"tagsIds":[...]}]
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/accounts/{accountRef}/projects/{projectRef}/tag-lists/{tagListRef}/tags/bulk-move:
    post:
      summary: Move tags between tag lists
      operationId: bulkMoveTags
      description: |
        <span class='tag beta'>BETA</span>Move tags from different tag lists to a common target tag list.

        - Requires the permission `workzone:tags:write` on the work zones of all tag lists
        - The project must have a valid subscription
        - All tag lists must not be pending
        - The target tag list must not exceed the maximum number of tags for a tag list (50000)
      tags:
        - Tag
      parameters:
        - $ref: '#/components/parameters/accountRef'
        - $ref: '#/components/parameters/projectRef'
        - $ref: '#/components/parameters/tagListRef'
      requestBody:
        description: |
          For each source tag list, the tags to move.

          An optional metadata id mapping can be specified for each list, to update the metadata ids inside the tags to move.
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/tagsMoveBulkRequest'
      responses:
        '200':
          description: |
            The tags are successfully moved.

            The response returns for each tag list (including all source lists and the target list),
            the new state of the tag list together with the state of the updated tags.

            The target tag list contains all moved tags as new tags, while each source tag list contains the tags moved
            from it as deleted in the list.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/tagsMoveBulkResponse'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...
            export ACCOUNTUUID=...
            export PROJECTUUID=...
            export TARGETTAGLISTUUID=...
            export SOURCETAGLISTUUID=...

            curl --request PUT --url "https://aec.cintoo.com/api/2/accounts/$ACCOUNTUUID/projects/$PROJECTUUID/tag-lists/$TARGETTAGLISTUUID" \
              --header "Authorization: Bearer $TOKEN" \
              --header "Content-Type: application/json" \
              --data "[{\"sourceTagListId\":\"$SOURCETAGLISTUUID\",\"tagsIds\":[...]}"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            accountUUID = "..."
            projectUUID = "..."
            targetListUUID = "..."
            token = "..."

            response = requests.post(
              "https://%s/api/2/accounts/%s/projects/%s/tag-lists/%s"
                % (host, accountUUID, projectUUID, targetListUUID),
              headers = { "Authorization": "Bearer %s" % token },
              json = [{
                "sourceTagListId": "...",
                "tagsIds": [...]
              }]
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const accountUUID = "...";
            const projectUUID = "...";
            const targetTagListUUID = "...";
            const token = "...";
            const sourceTagListUUID = "...";

            const url = `https://${host}/api/2/accounts/${accountUUID}/projects/${projectUUID}/tag-lists/${targetTagListUUID}`;
            const res = await fetch(url, {
              method: "POST",
              headers: {
                "Authorization": `Bearer ${token}`,
                "Content-Type": "application/json",
              },
              body: JSON.stringify(
                [{"sourceTagListId":sourceTagListUUID,"tagsIds":[...]}]
              ),
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/values/permissions:
    get:
      summary: List permissions
      operationId: listPermissions
      description: <span class='tag beta'>BETA</span>List available permissions
      tags:
        - Permissions
      responses:
        '200':
          description: List of permissions
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/Permission'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...

            curl --url "https://aec.cintoo.com/api/2/values/permissions" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            token = "..."

            response = requests.get(
              "https://%s/api/2/values/permissions" % host,
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const token = "...";

            const url = `https://${host}/api/2/values/permissions`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/values/formats:
    get:
      summary: List file formats
      operationId: listFormats
      description: <span class='tag beta'>BETA</span>List supported file formats
      tags:
        - File
      responses:
        '200':
          description: List of file formats
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/FileFormat'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
  /api/2/values/spatial-references:
    get:
      summary: List Spatial References
      operationId: listSpatialReferences
      description: <span class='tag beta'>BETA</span>List official Spatial References from https://spatialreference.org/
      tags:
        - Project
      responses:
        '200':
          description: List of Spatial References
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/SpatialReferenceValue'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...

            curl --url "https://aec.cintoo.com/api/2/values/spatial-references" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            token = "..."

            response = requests.get(
              "https://%s/api/2/values/spatial-references" % host,
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const token = "...";

            const url = `https://${host}/api/2/values/spatial-references`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/values/ai-detection-classes:
    get:
      summary: List AI detection classes
      operationId: listAIDetectionClasses
      description: <span class='tag beta'>BETA</span>List possible classes for AI detection
      tags:
        - Tag List
      responses:
        '200':
          description: List of classes
          content:
            application/json:
              schema:
                type: array
                items:
                  $ref: '#/components/schemas/AIDetectionClass'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...

            curl --url "https://aec.cintoo.com/api/2/values/ai-detection-classes" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            token = "..."

            response = requests.get(
              "https://%s/api/2/values/ai-detection-classes" % host,
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |

            const host = "aec.cintoo.com";
            const token = "...";

            const url = `https://${host}/api/2/values/ai-detection-classes`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
  /api/2/integrations/autodesk/connection:
    get:
      summary: Get my connection to Autodesk
      operationId: getAutodeskConnection
      description: |
        <span class='tag beta'>BETA</span>Get the Autodesk user connected for the current user

        Requires no permission, but must be logged in as a user.

        If no connection exists, an error 404 is returned. To create a connection, the user needs to follow the
        OAuth flow on Autodesk, using the [/integrations/autodesk/connection/url](#tag/AutodeskConnection/operation/getAutodeskConnectionUrl) endpoint.
      tags:
        - Autodesk
      responses:
        '200':
          description: Connection to Autodesk
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/AutodeskConnection'
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...

            curl --url "https://aec.cintoo.com/api/2/integrations/autodesk/connection" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            token = "..."

            response = requests.get(
              "https://%s/api/2/integrations/autodesk/connection"
                % (host),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
            print(response.json())
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const token = "...";

            const url = `https://${host}/api/2/integrations/autodesk/connection`;
            const res = await fetch(url, {
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            const data = await res.json();
            console.log(data);
    delete:
      summary: Disconnect me from Autodesk
      operationId: deleteAutodeskConnection
      description: |
        <span class='tag beta'>BETA</span>Delete connection on Autodesk for the current user

        Requires no permission, but must be logged in as a user.

        Removing the connection to Autodesk implies that annotations created by the user won't be synchronized on
        Autodesk for all accounts and all projects.
      tags:
        - Autodesk
      responses:
        '204':
          description: Connection successfully deleted
        4XX:
          $ref: '#/components/responses/UnexpectedError'
      x-codeSamples:
        - lang: cURL
          source: |
            export TOKEN=...

            curl --request DELETE --url "https://aec.cintoo.com/api/2/integrations/autodesk/connection" \
              --header "Authorization: Bearer $TOKEN"
        - lang: Python
          source: |
            import requests

            host = "aec.cintoo.com"
            token = "..."

            response = requests.delete(
              "https://%s/api/2/integrations/autodesk/connection"
                % (host),
              headers = { "Authorization": "Bearer %s" % token}
            )
            print(response.status_code)
        - lang: TypeScript
          source: |
            const host = "aec.cintoo.com";
            const token = "...";

            const url = `https://${host}/api/2/integrations/autodesk/connection`;
            const res = await fetch(url, {
              method: "DELETE",
              headers: {
                "Authorization": `Bearer ${token}`,
              },
            });

            console.log(res.status);
            if (res.status !== 204) {
              const data = await res.json();
              console.log(data);
            }
  /api/2/integrations/autodesk/connection/url:
    get:
      summary: Create or update Autodesk connection
      operationId: getAutodeskConnectionUrl
      description: |
        <span class='tag beta'>BETA</span>Forward to the OAuth flow on Autodesk. If the user successfully signed in Autodesk and allows Cintoo to
        access his account, his Autodesk connection is created or updated.

        This endpoint needs to be called using a browser to allow the user to sign in on Autodesk.

        On success, the user is finally redirected to the given query parameter `redirectUri`, allowing your application
        to be notified.

        Requires no permission, but must be logged in as a user.
      tags:
        - Autodesk
      parameters:
        - $ref: '#/components/parameters/redirectUri'
      responses:
        '302':
          description: Forward to the OAuth flow on Autodesk
        4XX:
          $ref: '#/components/responses/UnexpectedError'
components:
  parameters:
    accountRef:
      name: accountRef
      in: path
      required: true
      description: The Id or Urn of the account
      schema:
        oneOf:
          - $ref: '#/components/schemas/AccountUrn'
          - $ref: '#/components/schemas/AccountId'
    subscriptionRef:
      name: subscriptionRef
      in: path
      required: true
      description: The Id or Urn of the subscription
      schema:
        oneOf:
          - $ref: '#/components/schemas/SubscriptionUrn'
          - $ref: '#/components/schemas/SubscriptionId'
    userRef:
      name: userRef
      in: path
      required: true
      description: The Id or Urn of the user
      schema:
        $ref: '#/components/schemas/UserRef'
    groupRef:
      name: groupRef
      in: path
      required: true
      description: The Id or Urn of the group
      schema:
        $ref: '#/components/schemas/GroupRef'
    roleRef:
      name: roleRef
      in: path
      required: true
      description: The Id or Urn of the role
      schema:
        oneOf:
          - $ref: '#/components/schemas/RoleUrn'
          - $ref: '#/components/schemas/RoleId'
    accountIntegrationKonektRef:
      name: integrationRef
      in: path
      required: true
      description: The Urn of the integration
      schema:
        $ref: '#/components/schemas/AccountIntegrationKonektUrn'
    countScans:
      name: countScans
      in: query
      required: false
      description: |
        If true, includes `scanCount` and `scanSize` in the `statsInfo` field of the response.
        If false, these fields will not be included in `statsInfo`.

        **Upcoming change:** The default value will change from `true` to `false` in a future release.
        Clients that rely on `scanCount` or `scanSize` should start passing `countScans=true` explicitly.
      schema:
        type: boolean
        default: true
    countTags:
      name: countTags
      in: query
      required: false
      description: |
        If true, includes `tagCount` in the `statsInfo` field of the response.
        If false, this field will not be included in `statsInfo`.

        **Upcoming change:** The default value will change from `true` to `false` in a future release.
        Clients that rely on `tagCount` should start passing `countTags=true` explicitly.
      schema:
        type: boolean
        default: true
    countWorkzones:
      name: countWorkzones
      in: query
      required: false
      description: |
        If true, includes `workzoneCount` in the `statsInfo` field of the response.
        If false, this field will not be included in `statsInfo`.

        **Upcoming change:** The default value will change from `true` to `false` in a future release.
        Clients that rely on `workzoneCount` should start passing `countWorkzones=true` explicitly.
      schema:
        type: boolean
        default: true
    projectRef:
      name: projectRef
      in: path
      required: true
      description: The Id or Urn of the project
      schema:
        oneOf:
          - $ref: '#/components/schemas/ProjectUrn'
          - $ref: '#/components/schemas/ProjectId'
    permanentProjectDelete:
      name: permanent
      in: query
      required: false
      description: Delete Permanently a project
      schema:
        type: boolean
    workzoneRef:
      name: workzoneRef
      in: path
      required: true
      description: The Id or Urn of the work zone
      schema:
        oneOf:
          - $ref: '#/components/schemas/WorkzoneUrn'
          - $ref: '#/components/schemas/WorkzoneId'
    allowRemoveOnParents:
      name: allowRemoveOnParents
      in: query
      required: false
      description: |
        If true the operation is allowed to remove from a parent.

        If false and the operation needs to remove from a parent, a bad request error will be returned.
      schema:
        type: boolean
        default: false
    categoryFilter:
      name: category
      in: query
      required: false
      description: |
        Comma-separated list of case-sensitive categories of the files we want to list.

        Providing at least 1 invalid category will return a 400 Bad Request error.
        Providing empty parameter will return empty files list.
        If not provided, all files are returned.

        The category of an existing file can be found in its "type" field.
      schema:
        type: array
        items:
          type: string
          enum:
            - document
            - model
            - scan
            - media
            - miscfile
            - archive
            - geoImage
            - sitemap
            - modelReport
      examples:
        oneId:
          summary: Example of single category
          value:
            - scan
        multipleIds:
          summary: Example of multiple categories
          value:
            - geoImage
            - model
    fileRef:
      name: fileRef
      in: path
      required: true
      description: The Id or Urn of the file
      schema:
        oneOf:
          - $ref: '#/components/schemas/FileUrn'
          - $ref: '#/components/schemas/FileId'
    shareLinkRef:
      name: shareLinkRef
      in: path
      required: true
      description: The Urn of the share link
      schema:
        oneOf:
          - $ref: '#/components/schemas/ShareLinkUrn'
    includeComments:
      name: includeComments
      in: query
      required: false
      description: |
        If true the operation retrieves comments attached to each annotation,
        else the comments field will not be returned.
      schema:
        type: boolean
        default: false
    includeAttachments:
      name: includeAttachments
      in: query
      required: false
      description: |
        If true the operation retrieves information about files attached to each annotation or comment,
        else the attachments field will not be returned.
      schema:
        type: boolean
        default: false
    annotationRef:
      name: annotationRef
      in: path
      required: true
      description: The Id or Urn of the annotation
      schema:
        oneOf:
          - $ref: '#/components/schemas/AnnotationUrn'
          - $ref: '#/components/schemas/AnnotationId'
    workzonesFilter:
      name: workzones
      in: query
      required: false
      description: |
        Comma-separated list of work zone references.

        Providing at least 1 invalid (bad format, not exist, unreachable) work zone will return a 400 Bad Request error.
        Providing empty parameter will return empty crops list.
      schema:
        type: array
        items:
          $ref: '#/components/schemas/WorkzoneRef'
      examples:
        oneId:
          summary: Example of single work zone
          value:
            - 9260cd6f-a27b-456f-a5e3-b9c9fd4689f9
        multipleIds:
          summary: Example of multiple work zones
          value:
            - 9260cd6f-a27b-456f-a5e3-b9c9fd4689f9
            - urn:cintoo:workzone:e7868b89-80a6-429d-949f-66e3834651f2
    cropRef:
      name: cropRef
      in: path
      required: true
      description: The Id or Urn of the crop
      schema:
        oneOf:
          - $ref: '#/components/schemas/CropUrn'
          - $ref: '#/components/schemas/CropId'
    tagListRef:
      name: tagListRef
      in: path
      required: true
      description: The Id or Urn of the tag list
      schema:
        oneOf:
          - $ref: '#/components/schemas/TagListUrn'
          - $ref: '#/components/schemas/TagListId'
    afterVersion:
      name: afterVersion
      in: query
      required: false
      description: |
        If specified, return changes after the given version. A negative value or 0 is ignored.
      schema:
        type: integer
        default: 0
    redirectUri:
      name: redirectUri
      in: query
      required: true
      description: |
        The URL to redirect the user once the OAuth flow is successful.
      schema:
        type: string
  schemas:
    AccountUrn:
      type: string
      description: |
        "urn:cintoo:account:" followed by an UUIDv4
    DateTime:
      type: string
      format: date-time
      description: 'The date-time notation as defined by RFC 3339, section 5.6 (ex: 2017-08-18T12:41:31Z)'
      example: '2017-08-18T12:41:31Z'
    UserUrn:
      type: string
      description: |
        "urn:cintoo:user:" followed by an UUIDv4
    AccountPermissions:
      type: object
      required:
        - isOwner
        - isAdmin
        - isManager
        - isProjectLister
      properties:
        isOwner:
          type: boolean
        isAdmin:
          type: boolean
        isManager:
          type: boolean
        isProjectLister:
          type: boolean
    SubscriptionUrn:
      type: string
      description: |
        "urn:cintoo:subscription:" followed by an UUIDv4
    Subscription:
      type: object
      required:
        - id
        - type
        - accountId
        - isVa
        - isActive
        - isTrial
        - geoImageCapacity
        - tagCapacity
        - scanCapacity
      x-tags:
        - Subscription
      properties:
        id:
          readOnly: true
          $ref: '#/components/schemas/SubscriptionUrn'
        type:
          type: string
          enum:
            - subscription
        accountId:
          $ref: '#/components/schemas/AccountUrn'
        isVa:
          type: boolean
        isActive:
          type: boolean
        isTrial:
          type: boolean
          description: |
            Indicates whether this subscription is a trial (Free Plan).
        name:
          type: string
          example: My Subscription
        start:
          $ref: '#/components/schemas/DateTime'
        end:
          $ref: '#/components/schemas/DateTime'
          description: |
            Subscription end date. This property is only included when the requester has the `account:subscriptions:read-sensitive-infos` permission (account owner or administrator).
        tagCapacity:
          type: integer
          example: 50
        geoImageCapacity:
          type: integer
          example: 50
        scanCapacity:
          type: integer
          example: 500
        createdAt:
          $ref: '#/components/schemas/DateTime'
        updatedAt:
          $ref: '#/components/schemas/DateTime'
        inviteAccountOwnerInNewProjects:
          type: boolean
          description: |
            When true, the account owner is automatically invited to every new project in the subscription. This property is only included in responses when the requester is the account owner.
    Region:
      type: object
      required:
        - id
        - description
        - provider
        - hasCdn
        - selected
        - default
      x-tags:
        - Region
      properties:
        id:
          type: string
        description:
          type: string
        provider:
          description: The storage used in this region
          type: string
          enum:
            - azure
            - s3
            - minio
        cloudProvider:
          description: The Cloud Platform managing this region
          type: string
          enum:
            - azure
            - s3
            - gcp
            - minio
        region:
          description: A unique region identifier used by the Cloud Platform
          type: string
        countryCode:
          description: The country in which the region is physically located
          type: string
        hasCdn:
          description: true if a CDN system is enabled with this region
          type: boolean
        selected:
          description: true if the region is available for this account
          type: boolean
        default:
          description: true if this region is the default one for this account
          type: boolean
    Account:
      type: object
      required:
        - id
        - type
        - name
        - createdAt
        - updatedAt
        - ownerId
        - permissions
        - subscriptions
        - regions
      x-tags:
        - Account
      properties:
        id:
          readOnly: true
          $ref: '#/components/schemas/AccountUrn'
        type:
          type: string
          enum:
            - account
        name:
          type: string
          example: My Account
        createdAt:
          $ref: '#/components/schemas/DateTime'
        updatedAt:
          $ref: '#/components/schemas/DateTime'
        ownerId:
          $ref: '#/components/schemas/UserUrn'
        permissions:
          $ref: '#/components/schemas/AccountPermissions'
        subscriptions:
          type: array
          items:
            $ref: '#/components/schemas/Subscription'
        regions:
          type: array
          items:
            $ref: '#/components/schemas/Region'
    Error:
      type: object
      required:
        - status
        - title
      properties:
        status:
          type: integer
          format: int32
        title:
          type: string
        detail:
          type: string
          description: human readable description of the error in english
        errorCode:
          type: string
          description: detailed code describing error, can be used to map to a localized message
        errorValues:
          type: object
          description: values implied in the error, typically an input parameter, can be used to include in a localized message
    Uuid:
      type: string
      description: UUIDv4
      format: uuid
      example: 156fff1a-0ef7-4335-891a-627928a19e29
    AccountId:
      allOf:
        - $ref: '#/components/schemas/Uuid'
      example: 8196e320-8a4d-4d29-b0a9-077cd7ed3368
    SubscriptionId:
      allOf:
        - $ref: '#/components/schemas/Uuid'
      example: 8196e320-8a4d-4d29-b0a9-077cd7ed3368
    date:
      type: string
      format: date
      description: 'The date-time notation as defined by RFC 3339, section 5.6 (ex: 2017-08-18)'
      example: '2017-07-21'
    Date:
      type: string
      format: date
      description: 'The date-time notation as defined by RFC 3339, section 5.6 (ex: 2017-08-18)'
      example: '2017-07-21'
    ProjectUrn:
      type: string
      description: |
        "urn:cintoo:project:" followed by an UUIDv4
    UsageReport:
      type: object
      x-tags:
        - Usage Report
      required:
        - date
        - userId
        - firstName
        - lastName
        - email
        - company
        - projectId
        - projectName
        - scansUploaded
        - scansDownloaded
        - 360ImagesUploaded
        - cropsDownloaded
        - unifiedMeshesCreated
        - tagsUploaded
        - tagsDownloaded
      properties:
        date:
          $ref: '#/components/schemas/Date'
        userId:
          $ref: '#/components/schemas/UserUrn'
        firstName:
          type: string
        lastName:
          type: string
        email:
          type: string
        company:
          type: string
        projectId:
          $ref: '#/components/schemas/ProjectUrn'
        projectName:
          type: string
        scansUploaded:
          type: integer
        scansDownloaded:
          type: integer
        360ImagesUploaded:
          type: integer
        cropsDownloaded:
          type: integer
        unifiedMeshesCreated:
          type: integer
        tagsUploaded:
          type: integer
        tagsDownloaded:
          type: integer
    AccountRoles:
      type: string
      enum:
        - administrator
        - projectLister
        - projectManager
    UserOldId:
      type: string
      pattern: ^[-_0-9a-zA-Z]+$
      description: Base64url encoding of "user-{id}"
    User:
      type: object
      required:
        - id
        - type
        - api1Id
        - firstName
        - email
      x-tags:
        - User
      properties:
        id:
          readOnly: true
          $ref: '#/components/schemas/UserUrn'
        type:
          type: string
          enum:
            - user
        api1Id:
          $ref: '#/components/schemas/UserOldId'
        firstName:
          type: string
          example: John
        lastName:
          type: string
          example: Doe
        email:
          type: string
          format: email
        company:
          type: string
          example: Cintoo
        title:
          type: string
          example: Account Manager
        avatar:
          type: string
          format: uri
        accountRoles:
          type: array
          description: |
            Account-level roles assigned to this user. Visibility depends on caller's permissions:

            Visibility rules:
            - Field is omitted if caller has none of the role read permissions
            - Field is included with filtered roles based on caller's permissions:
              - `account:administrators:read`: can see administrator role
              - `account:project-managers:read`: can see projectManager role
              - `account:project-listers:read`: can see projectLister role

            Examples:
            - Account Owner/Admin (has all three read permissions): sees all roles (administrator, projectManager, projectLister)
            - Project Manager (has only project-managers:read): sees only projectManager role
            - Normal user (has no read permissions): field is omitted entirely

            Note: The accountOwner role is never exposed in this field.
          items:
            $ref: '#/components/schemas/AccountRoles'
          example:
            - administrator
            - projectManager
    UserId:
      allOf:
        - $ref: '#/components/schemas/Uuid'
      example: 8196e320-8a4d-4d29-b0a9-077cd7ed3368
    UserRef:
      oneOf:
        - $ref: '#/components/schemas/UserUrn'
        - $ref: '#/components/schemas/UserId'
    GroupUrn:
      type: string
      description: |
        "urn:cintoo:group:" followed by an UUIDv4
    Color:
      type: string
      pattern: ^#[0-9a-f]{6}$
      example: '#0698ec'
    Group:
      type: object
      required:
        - type
        - name
        - color
        - createdAt
        - createdBy
        - updatedAt
        - userIds
        - accountIds
      x-tags:
        - Group
      properties:
        id:
          readOnly: true
          $ref: '#/components/schemas/GroupUrn'
        type:
          type: string
          enum:
            - group
        name:
          type: string
          example: group
        description:
          type: string
          example: group description
        color:
          $ref: '#/components/schemas/Color'
        createdAt:
          $ref: '#/components/schemas/DateTime'
        createdBy:
          $ref: '#/components/schemas/UserUrn'
        updatedAt:
          $ref: '#/components/schemas/DateTime'
        userIds:
          type: array
          items:
            $ref: '#/components/schemas/UserUrn'
        accountIds:
          type: array
          items:
            $ref: '#/components/schemas/AccountUrn'
    GroupId:
      allOf:
        - $ref: '#/components/schemas/Uuid'
      example: 8196e320-8a4d-4d29-b0a9-077cd7ed3368
    GroupRef:
      oneOf:
        - $ref: '#/components/schemas/GroupUrn'
        - $ref: '#/components/schemas/GroupId'
    ErrorBadRequest:
      type: object
      required:
        - status
        - title
      properties:
        status:
          type: integer
          format: int32
          enum:
            - 400
        title:
          type: string
          enum:
            - Bad Request
        detail:
          type: string
          description: human readable description of the error in english
    RoleUrn:
      type: string
      description: |
        "urn:cintoo:role:" followed by an UUIDv4
    RolePermissions:
      type: array
      description: permissions given by the role. Please check the [permissions](#section/API-Specification/Permissions) section for the list of allowed combinations
      minItems: 1
      items:
        type: string
        enum:
          - project:project:delete
          - project:project:update-details
          - workzone:workzones:write
          - workzone:workzones:read
          - workzone:annotations:write
          - workzone:annotations:read
          - workzone:measurements:write
          - workzone:measurements:read
          - workzone:share-links:write
          - workzone:share-links:read
          - workzone:own-share-links:write
          - workzone:own-share-links:read
          - workzone:reality-data:read
          - workzone:reality-data:write
          - workzone:reality-data:transform
          - workzone:cad-model:transform
          - workzone:cad-model:read
          - workzone:import-jobs-reality-data:write
          - workzone:export-jobs-reality-data:read
          - workzone:export-jobs-reality-data:write
          - workzone:documents:write
          - workzone:documents:read
          - workzone:members:write
          - workzone:tags:write
          - workzone:tags:read
          - workzone:savedviews:write
          - workzone:savedviews:read
          - workzone:model-reports:read
          - workzone:model-reports:write
          - workzone:progress-monitoring-jobs:read
          - workzone:progress-monitoring-jobs:write
          - workzone:own-progress-monitoring-jobs:read
          - workzone:own-progress-monitoring-jobs:write
    Role:
      type: object
      required:
        - id
        - type
        - name
        - createdBy
        - createdAt
        - updatedAt
        - permissions
        - accountIds
      x-tags:
        - Role
      properties:
        id:
          readOnly: true
          $ref: '#/components/schemas/RoleUrn'
        type:
          type: string
          enum:
            - role
        name:
          type: string
          example: BIM / VDC Manager
        description:
          type: string
          example: Can do anything except creating projects
        color:
          $ref: '#/components/schemas/Color'
        createdBy:
          $ref: '#/components/schemas/UserUrn'
        createdAt:
          $ref: '#/components/schemas/DateTime'
        updatedAt:
          $ref: '#/components/schemas/DateTime'
        permissions:
          $ref: '#/components/schemas/RolePermissions'
        accountIds:
          type: array
          items:
            $ref: '#/components/schemas/AccountUrn'
    roleCreate:
      type: object
      required:
        - name
        - permissions
      properties:
        name:
          type: string
          example: BIM / VDC Manager
        description:
          type: string
          example: Can do anything except creating projects
        color:
          $ref: '#/components/schemas/Color'
        permissions:
          $ref: '#/components/schemas/RolePermissions'
    ErrorForbidden:
      type: object
      required:
        - status
        - title
      properties:
        status:
          type: integer
          format: int32
          enum:
            - 403
        title:
          type: string
          enum:
            - Forbidden
        detail:
          type: string
          description: human readable description of the error in english
    RoleId:
      allOf:
        - $ref: '#/components/schemas/Uuid'
      example: 8196e320-8a4d-4d29-b0a9-077cd7ed3368
    roleUpdate:
      type: object
      properties:
        name:
          type: string
          example: BIM / VDC Manager
        description:
          type: string
          example: Can do anything except creating projects
        color:
          $ref: '#/components/schemas/Color'
        permissions:
          $ref: '#/components/schemas/RolePermissions'
    AccountIntegrationKonektUrn:
      type: string
      description: |
        "urn:cintoo:newforma-konekt-hub:" followed by an UUIDv4
    ModificationInfo:
      type: object
      required:
        - createdAt
        - createdBy
      properties:
        createdAt:
          $ref: '#/components/schemas/DateTime'
        createdBy:
          $ref: '#/components/schemas/UserUrn'
        updatedAt:
          $ref: '#/components/schemas/DateTime'
        updatedBy:
          $ref: '#/components/schemas/UserUrn'
        deletedAt:
          $ref: '#/components/schemas/DateTime'
        deletedBy:
          $ref: '#/components/schemas/UserUrn'
        isDeleted:
          type: boolean
    AccountIntegrationNewformaKonektOptions:
      type: object
      required:
        - allowCreateNewUsersOnHub
        - syncStrategy
      properties:
        allowCreateNewUsersOnHub:
          type: boolean
          description: if true it allows to create automatically new users on the Konekt Hub when synchronizing data
        syncStrategy:
          type: string
          enum:
            - CONFIG_ON_PROJECT
            - BOTH_WAY
            - UPDATES_ONLY_FROM_KONEKT
          description: |
            CONFIG_ON_PROJECT means the option needs to be configured on each project.

            BOTH_WAY means an update on Cintoo triggers an update on Konekt.

            UPDATES_ONLY_FROM_KONEKT means an update on Cintoo does not trigger an update on Konekt.

            BOTH_WAY and UPDATES_ONLY_FROM_KONEKT means an update on Konekt will trigger an update on Cintoo.
    ProjectInKonektHub:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: string
          description: id of the project on Konekt
        name:
          type: string
          description: name of the project on Konekt
    ProjectIntegrationNewformaKonektOptions:
      type: object
      required:
        - allowInviteUsers
        - syncStrategy
      properties:
        allowInviteUsers:
          type: boolean
          description: if true it allows to invite users on the Konekt project when synchronizing data
        syncStrategy:
          type: string
          enum:
            - BOTH_WAY
            - UPDATES_ONLY_FROM_KONEKT
          description: |
            BOTH_WAY means an update on Cintoo triggers an update on Konekt.

            UPDATES_ONLY_FROM_KONEKT means an update on Cintoo does not trigger an update on Konekt.

            BOTH_WAY and UPDATES_ONLY_FROM_KONEKT means an update on Konekt will trigger an update on Cintoo.
    AccountIntegrationNewformaKonektLinkedProject:
      type: object
      properties:
        id:
          $ref: '#/components/schemas/ProjectUrn'
        konektId:
          type: number
        options:
          $ref: '#/components/schemas/ProjectIntegrationNewformaKonektOptions'
    AccountIntegrationKonekt:
      type: object
      x-tags:
        - Konekt
      properties:
        id:
          $ref: '#/components/schemas/AccountIntegrationKonektUrn'
        konektId:
          type: string
          description: id of the hub on Konekt
        beta:
          type: boolean
          description: indicates if the hub is on Konekt beta or Konekt production
        modificationInfo:
          $ref: '#/components/schemas/ModificationInfo'
        link:
          type: string
        options:
          $ref: '#/components/schemas/AccountIntegrationNewformaKonektOptions'
        name:
          type: string
          nullable: true
          description: name of the hub on Konekt if available
        connected:
          type: boolean
          description: indicates if a valid token is configured
        konektProjects:
          type: array
          items:
            $ref: '#/components/schemas/ProjectInKonektHub'
          description: list of projects on the Konekt Hub
        linkedProjects:
          type: array
          items:
            $ref: '#/components/schemas/AccountIntegrationNewformaKonektLinkedProject'
          description: Cintoo projects configured with this Konekt Hub
    AccountIntegrations:
      type: object
      properties:
        konekt:
          type: array
          items:
            $ref: '#/components/schemas/AccountIntegrationKonekt'
    accountIntegrationKonektCreate:
      type: object
      required:
        - token
      properties:
        token:
          type: string
          description: A valid API token for Newforma Konekt
        options:
          allOf:
            - $ref: '#/components/schemas/AccountIntegrationNewformaKonektOptions'
          type: object
          nullable: true
    ErrorConflict:
      type: object
      required:
        - status
        - title
      properties:
        status:
          type: integer
          format: int32
          enum:
            - 409
        title:
          type: string
          enum:
            - Conflict
        detail:
          type: string
          description: human readable description of the error in english
    accountIntegrationKonektUpdate:
      type: object
      required:
        - options
      properties:
        options:
          $ref: '#/components/schemas/AccountIntegrationNewformaKonektOptions'
    accountIntegrationConnectionKonektCreate:
      type: object
      required:
        - token
      properties:
        token:
          type: string
          description: A valid API token for Newforma Konekt
    Location:
      type: object
      required:
        - lat
        - lng
      properties:
        lat:
          type: number
          format: double
          minimum: -90
          maximum: 90
        lng:
          type: number
          format: double
          minimum: -180
          maximum: 180
    ProjectStatsInfo:
      type: object
      description: |
        Statistics about the project. By default all fields are included.
        Use the query parameters `countScans`, `countTags`, and `countWorkzones` to control which fields are returned.
        When all query parameters are set to `false`, `statsInfo` is `null`.

        **Upcoming change:** In a future release, all `count*` query parameters will default to `false`,
        meaning `statsInfo` will be `null` by default. Clients that need statistics should start passing the
        desired `count*` parameters explicitly set to `true`.
      properties:
        scanCount:
          type: integer
        scanSize:
          type: integer
        tagCount:
          type: integer
        workzoneCount:
          type: integer
          description: Number of workzones in a project (includes root workzone)
    BlobName:
      type: string
      description: |
        A `BlobName`: the actual filename of the file in the blob storage.

        To retrieve a blob, two methods are available:
        - using the unitary endpoint [Download a blob](#tag/Project/operation/getBlob), which will automatically forward
          to the final URL, which is a pre-signed URL, expiring after 3 hours by default.
        - using the bulk endpoint [Get pre-signed urls for multiple blobs](#tag/Project/operation/getBlobs), which will
          return the list of pre-signed URLs.

        While the first option is the easiest, as it forwards automatically to the final URL transparently,
        the second option should be preferred as much as possible to improve performances.
    Url:
      type: string
      format: uri
      description: Link that will redirect to 302 signed link to download the corresponding file.
    WorkzoneUrn:
      type: string
      pattern: ^urn:cintoo:workzone:[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$
      description: |
        "urn:cintoo:workzone:" followed by an UUIDv4
    SpatialReferenceProject:
      type: object
      required:
        - authName
        - code
      properties:
        authName:
          type: string
          description: The authority defining the used code (IGNF, NKG, OGC, IAU_2015, EPSG, ESRI)
        code:
          type: string
          description: The code of the geographic reference to be used for this project
    Project:
      type: object
      required:
        - id
        - type
        - accountId
        - subscriptionId
        - isdemo
        - region
        - name
        - description
        - ownerId
        - location
        - modificationInfo
        - statsInfo
        - rootWorkzoneId
        - permissions
      x-tags:
        - Project
      properties:
        id:
          readOnly: true
          $ref: '#/components/schemas/ProjectUrn'
        type:
          type: string
          enum:
            - project
        accountId:
          $ref: '#/components/schemas/AccountUrn'
        subscriptionId:
          $ref: '#/components/schemas/SubscriptionUrn'
        isdemo:
          type: boolean
        region:
          type: string
          description: Region.Id (can be found in accounts.regions.id) hosting the project data
          example: default
        name:
          type: string
          description: Named displayed for the project
          example: New Project
        description:
          type: string
        ownerId:
          $ref: '#/components/schemas/UserUrn'
        location:
          $ref: '#/components/schemas/Location'
        modificationInfo:
          $ref: '#/components/schemas/ModificationInfo'
        statsInfo:
          type: object
          nullable: true
          allOf:
            - $ref: '#/components/schemas/ProjectStatsInfo'
        coverBlobName:
          type: string
          nullable: true
          allOf:
            - $ref: '#/components/schemas/BlobName'
        coverUrl:
          type: string
          nullable: true
          allOf:
            - $ref: '#/components/schemas/Url'
        rootWorkzoneId:
          $ref: '#/components/schemas/WorkzoneUrn'
        spatialReference:
          type: object
          nullable: true
          allOf:
            - $ref: '#/components/schemas/SpatialReferenceProject'
        permissions:
          type: array
          items:
            description: list of permissions at project level, for the user doing the request.
            type: string
        bimOnRootWz:
          type: boolean
          nullable: true
          description: |
            Indicates whether the requesting user has the BIM role on the root workzone of this project.
            - `true`: the user has the BIM role on the root workzone
            - `false`: the user does not have the BIM role on the root workzone
            - `null`: the user does not have permission to see this information (requires `account:projects:manage-me-as-bim` permission)
    SpatialReference:
      type: object
      required:
        - authName
        - code
      properties:
        authName:
          type: string
          minLength: 1
          maxLength: 128
          description: The authority defining the used code (IGNF, NKG, OGC, IAU_2015, EPSG, ESRI)
        code:
          type: string
          minLength: 1
          maxLength: 128
          description: The code of the geographic reference to be used for this project
    ProjectId:
      allOf:
        - $ref: '#/components/schemas/Uuid'
      example: 8196e320-8a4d-4d29-b0a9-077cd7ed3368
    SubscriptionRef:
      oneOf:
        - $ref: '#/components/schemas/SubscriptionUrn'
        - $ref: '#/components/schemas/SubscriptionId'
    FileUrn:
      type: string
      description: |
        "urn:cintoo:file:" followed by an UUIDv4
    WorkzoneStatsInfo:
      type: object
      required:
        - scanCount
        - scanSize
        - tagCount
      properties:
        scanCount:
          type: integer
        scanSize:
          type: integer
        tagCount:
          type: integer
    Workzone:
      type: object
      required:
        - id
        - type
        - projectId
        - rootWorkzoneId
        - name
        - description
        - roleIds
        - modificationInfo
        - statsInfo
      x-tags:
        - Workzone
      properties:
        id:
          readOnly: true
          $ref: '#/components/schemas/WorkzoneUrn'
        type:
          type: string
          enum:
            - workzone
        projectId:
          $ref: '#/components/schemas/ProjectUrn'
        parentId:
          allOf:
            - $ref: '#/components/schemas/WorkzoneUrn'
            - type: string
              nullable: true
          description: '`WorkzoneUrn` of the parent Workzone. Null if the work zone is a rootWorkzone.'
        rootWorkzoneId:
          $ref: '#/components/schemas/WorkzoneUrn'
        name:
          type: string
          example: New Workzone
        description:
          type: string
        roleIds:
          type: array
          items:
            $ref: '#/components/schemas/RoleUrn'
        coverBlobName:
          allOf:
            - $ref: '#/components/schemas/BlobName'
          type: string
          nullable: true
        coverUrl:
          type: string
          nullable: true
        modificationInfo:
          $ref: '#/components/schemas/ModificationInfo'
        statsInfo:
          $ref: '#/components/schemas/WorkzoneStatsInfo'
    WorkzoneId:
      allOf:
        - $ref: '#/components/schemas/Uuid'
      example: 8196e320-8a4d-4d29-b0a9-077cd7ed3368
    WorkzoneRef:
      oneOf:
        - $ref: '#/components/schemas/WorkzoneUrn'
        - $ref: '#/components/schemas/WorkzoneId'
    workzoneCreate:
      type: object
      required:
        - name
        - parentWorkzoneId
      properties:
        name:
          type: string
        parentWorkzoneId:
          $ref: '#/components/schemas/WorkzoneRef'
        description:
          type: string
        coverUrl:
          type: string
    RoleRef:
      oneOf:
        - $ref: '#/components/schemas/RoleUrn'
        - $ref: '#/components/schemas/RoleId'
    Archive:
      allOf:
        - $ref: '#/components/schemas/FileBase'
        - $ref: '#/components/schemas/FileBlob'
        - type: object
          required:
            - type
          properties:
            type:
              $ref: '#/components/schemas/FileCategoryArchive'
    Document:
      allOf:
        - $ref: '#/components/schemas/FileBase'
        - $ref: '#/components/schemas/FileBlob'
        - type: object
          required:
            - type
          properties:
            type:
              $ref: '#/components/schemas/FileCategoryDocument'
    GeoImage:
      allOf:
        - $ref: '#/components/schemas/FileBase'
        - $ref: '#/components/schemas/FileBlob'
        - $ref: '#/components/schemas/FileImported'
        - $ref: '#/components/schemas/FilePlaced'
        - type: object
          required:
            - type
            - resolution
            - blobPreview
            - urlPreview
            - panoramaWidth
            - panoramaHeight
          properties:
            type:
              $ref: '#/components/schemas/FileCategoryGeoImage'
            resolution:
              type: object
              allOf:
                - $ref: '#/components/schemas/Dimension2d'
              description: |
                The image's resolution: has a `w` and a `h`, respectively for *width* and *height*. This is not the same as `PanoramaWidth` and `PanoramaHeight`
            scanInfo:
              type: string
              nullable: true
            blobPreview:
              $ref: '#/components/schemas/BlobName'
            urlPreview:
              $ref: '#/components/schemas/Url'
            panoramaWidth:
              type: number
              format: int
              description: The image's *width*, in pixels
            panoramaHeight:
              type: number
              format: int
              description: The image's *height*, in pixels
            src:
              type: object
              nullable: true
              description: Contains metadata usable by the Cintoo Viewer
              properties:
                idd:
                  type: object
                  nullable: true
                  properties:
                    scan_id:
                      type: string
                    idd_type:
                      type: string
                      example: Corrosion
    Media:
      allOf:
        - $ref: '#/components/schemas/FileBase'
        - $ref: '#/components/schemas/FileBlob'
        - type: object
          required:
            - type
          properties:
            type:
              $ref: '#/components/schemas/FileCategoryMedia'
    MiscFile:
      allOf:
        - $ref: '#/components/schemas/FileBase'
        - $ref: '#/components/schemas/FileBlob'
        - type: object
          required:
            - type
          properties:
            type:
              $ref: '#/components/schemas/FileCategoryMiscFile'
    Model:
      allOf:
        - $ref: '#/components/schemas/FileBase'
        - $ref: '#/components/schemas/FileBlob'
        - $ref: '#/components/schemas/FileImported'
        - $ref: '#/components/schemas/FileTransformable'
        - type: object
          required:
            - type
            - translation
          properties:
            type:
              $ref: '#/components/schemas/FileCategoryModel'
            translation: {}
            src:
              description: Metadata attached to the model. Could be present or not.
              anyOf:
                - type: object
                  description: Contains metadata usable by the Cintoo Viewer
                  nullable: true
                - type: object
                  properties:
                    mfi:
                      type: object
                      description: The MFI location and its format
                      required:
                        - blobName
                        - format
                      properties:
                        blobName:
                          $ref: '#/components/schemas/BlobName'
                        format:
                          enum:
                            - json-br-1
                          description: the format of the Model MFI
    ModelReport:
      allOf:
        - $ref: '#/components/schemas/FileBase'
        - $ref: '#/components/schemas/FileBlobOptional'
        - type: object
          required:
            - type
          properties:
            type:
              $ref: '#/components/schemas/FileCategoryModelReport'
            src:
              $ref: '#/components/schemas/FileSrcModelReports'
    Scan:
      allOf:
        - $ref: '#/components/schemas/FileBase'
        - $ref: '#/components/schemas/FileBlob'
        - $ref: '#/components/schemas/FileImported'
        - $ref: '#/components/schemas/FileTransformable'
        - type: object
          required:
            - type
            - translation
            - rotation
            - resolution
            - blobPreview
            - urlPreview
            - panoramaWidth
            - panoramaHeight
            - azimuthLeft
            - azimuthRight
            - elevationTop
            - elevationBottom
            - appliedScaleFactor
          properties:
            translation: {}
            rotation: {}
            type:
              $ref: '#/components/schemas/FileCategoryScan'
            resolution:
              type: object
              allOf:
                - $ref: '#/components/schemas/Dimension2d'
              description: |
                The scan's resolution: has a `w` and a `h`, respectively for *width* and *height*. This is not the same as `PanoramaWidth` and `PanoramaHeight`
            scanInfo:
              type: string
              nullable: true
            blobPreview:
              $ref: '#/components/schemas/BlobName'
            urlPreview:
              $ref: '#/components/schemas/Url'
            panoramaWidth:
              type: number
              format: int
              description: The image's *width*, in pixels
            panoramaHeight:
              type: number
              format: int
              description: The image's *height*, in pixels
            azimuthLeft:
              type: number
              format: float
              description: |
                The max left angle of a scan coverage on the azimuth, in radians.
                For instance, a scan with 360 degrees coverage will have -π if the `azimuthLeft` is π
            azimuthRight:
              type: number
              format: float
              description: |
                The max right angle of a scan coverage on the azimuth, in radians.
                For instance, a scan with 360 degrees coverage will have π if the `azimuthRight` is -π
            elevationTop:
              type: number
              format: float
              description: |
                The max top angle of a scan coverage on the elevation, in radians. For example, a scan with 180 degrees coverage will have π/2 if the `elevationBottom` is -π/2
            elevationBottom:
              type: number
              format: float
              description: |
                The max bottom angle of a scan coverage on the elevation, in radians. For example, a scan with 180 degrees coverage will have -π/2 if the `elevationTop` is π/2
            appliedScaleFactor:
              type: number
              format: int
              description: |
                Scale value applied while the scans are imported through Cintoo Connect. Mainly used for conversion unit between imperial system and metric system.
            src:
              type: object
              nullable: true
              description: Contains metadata usable by the Cintoo Viewer
    SiteMap:
      allOf:
        - $ref: '#/components/schemas/FileBase'
        - $ref: '#/components/schemas/FileBlob'
        - type: object
          required:
            - type
          properties:
            type:
              $ref: '#/components/schemas/FileCategorySiteMap'
    FileCategoryGeoImage:
      type: string
      enum:
        - geoImage
      description: 'Geo Image files: `.idd`, `.360`'
    FileCategoryModel:
      type: string
      enum:
        - model
      description: 'Model files: `.3ds`, `.dae`, `.dxf`, `.ifc`, `.dwg`, `.rvt`, etc.'
    FileCategoryScan:
      type: string
      enum:
        - scan
      description: 'Scan files: `.fls`, `.e57`, etc.'
    FileCategoryModelReport:
      type: string
      enum:
        - modelReport
      description: 'Model report files: `.cipm`'
    FileCategoryDocument:
      type: string
      enum:
        - document
      description: 'Document files: `.doc[x]`, `.xls[x]`, `.ppt[x]`, `.txt`, `.rtf`, `.odt`, `.pdt`, `.pub`, `.csv`'
    FileCategoryMedia:
      type: string
      enum:
        - media
      description: 'Media files: `.png`, `.jp[e]g`, `.gif`, `.bmp`, `.tiff`'
    FileCategoryMiscFile:
      type: string
      enum:
        - miscfile
      description: Any other type of files
    FileCategoryArchive:
      type: string
      enum:
        - archive
      description: 'Archive files: `zip`'
    FileCategorySiteMap:
      type: string
      enum:
        - sitemap
      description: 'Site Map files: `.map`'
    FileCategoryVideo3d:
      type: string
      enum:
        - video3d
      description: 'Video3d files: `.mp4`'
    FileCategoryGaussian:
      type: string
      enum:
        - gaussian
      description: 'Gaussian Splatting files: `.ply`, `.spz`'
    FileCategory:
      oneOf:
        - $ref: '#/components/schemas/FileCategoryGeoImage'
        - $ref: '#/components/schemas/FileCategoryModel'
        - $ref: '#/components/schemas/FileCategoryScan'
        - $ref: '#/components/schemas/FileCategoryModelReport'
        - $ref: '#/components/schemas/FileCategoryDocument'
        - $ref: '#/components/schemas/FileCategoryMedia'
        - $ref: '#/components/schemas/FileCategoryMiscFile'
        - $ref: '#/components/schemas/FileCategoryArchive'
        - $ref: '#/components/schemas/FileCategorySiteMap'
        - $ref: '#/components/schemas/FileCategoryVideo3d'
        - $ref: '#/components/schemas/FileCategoryGaussian'
    FileId:
      allOf:
        - $ref: '#/components/schemas/Uuid'
      example: 8196e320-8a4d-4d29-b0a9-077cd7ed3368
    FormatUrn:
      type: string
      description: |
        "urn:cintoo:format:" followed by a number
    FileBase:
      type: object
      required:
        - id
        - type
        - api1Id
        - projectId
        - workzoneId
        - name
        - formatId
        - size
        - createdAt
        - createdBy
        - updatedAt
        - updatedBy
      properties:
        id:
          $ref: '#/components/schemas/FileUrn'
        type:
          $ref: '#/components/schemas/FileCategory'
        api1Id:
          $ref: '#/components/schemas/FileId'
        projectId:
          $ref: '#/components/schemas/ProjectUrn'
        workzoneId:
          $ref: '#/components/schemas/WorkzoneUrn'
        name:
          type: string
          example: fileName
        formatId:
          $ref: '#/components/schemas/FormatUrn'
        size:
          type: number
          format: int
          minimum: 0
          description: File size in Bytes
        createdAt:
          $ref: '#/components/schemas/DateTime'
        createdBy:
          $ref: '#/components/schemas/UserUrn'
        updatedAt:
          type: string
          allOf:
            - $ref: '#/components/schemas/DateTime'
          nullable: true
        updatedBy:
          type: string
          allOf:
            - $ref: '#/components/schemas/UserUrn'
          nullable: true
    FileBlob:
      type: object
      required:
        - blob
        - blobName
        - url
      properties:
        blob:
          type: string
          deprecated: true
          description: use `blobName` instead. `blob` will be removed the 1st of november 2025
          allOf:
            - $ref: '#/components/schemas/BlobName'
        blobName:
          $ref: '#/components/schemas/BlobName'
        url:
          $ref: '#/components/schemas/Url'
    FileImported:
      type: object
      required:
        - importedAt
      properties:
        importedAt:
          type: string
          nullable: true
          allOf:
            - $ref: '#/components/schemas/DateTime'
    Vector3D:
      type: object
      required:
        - x
        - 'y'
        - z
      properties:
        x:
          type: number
          format: double
        'y':
          type: number
          format: double
        z:
          type: number
          format: double
    Quaternion:
      type: object
      required:
        - x
        - 'y'
        - z
        - w
      properties:
        x:
          type: number
          format: float
        'y':
          type: number
          format: float
        z:
          type: number
          format: float
        w:
          type: number
          format: float
    FilePlaced:
      type: object
      required:
        - translation
        - rotation
      properties:
        translation:
          type: object
          allOf:
            - $ref: '#/components/schemas/Vector3D'
          description: |
            A vector representing the position of the image in the Workzone,
            with three components in meter: `x`, `y` and `z`
        rotation:
          type: object
          allOf:
            - $ref: '#/components/schemas/Quaternion'
          description: |
            A quaternion representing the rotation of the image,
            with the pivot point being its current position defined by translation.
            It has four components: `x`, `y`, `z` and `w`
          nullable: true
    Dimension2d:
      type: object
      required:
        - w
        - h
      properties:
        w:
          type: integer
        h:
          type: integer
    FileTransformable:
      type: object
      properties:
        translation:
          type: object
          allOf:
            - $ref: '#/components/schemas/Vector3D'
          description: |
            A vector representing the position in the Workzone,
            with three components in meter: `x`, `y` and `z`
          nullable: true
        rotation:
          type: object
          allOf:
            - $ref: '#/components/schemas/Quaternion'
          description: |
            A quaternion representing the rotation,
            with the pivot point being its current position defined by translation.
            It has four components: `x`, `y`, `z` and `w`
          nullable: true
        scale:
          type: object
          allOf:
            - $ref: '#/components/schemas/Vector3D'
          description: |
            A vector representing a scale deformation along the three axis
            with three ratio components : `x`, `y` and `z`
          nullable: true
    FileBlobOptional:
      type: object
      properties:
        blob:
          type: string
          nullable: true
          deprecated: true
          description: use `blobName` instead. `blob` will be removed the 1st of november 2025
          allOf:
            - $ref: '#/components/schemas/BlobName'
        blobName:
          type: string
          nullable: true
          allOf:
            - $ref: '#/components/schemas/BlobName'
        url:
          type: string
          nullable: true
          allOf:
            - $ref: '#/components/schemas/Url'
    FileSrcModelReports:
      type: object
      required:
        - model
        - elements
        - scans
        - tolerance
      properties:
        model:
          $ref: '#/components/schemas/FileUrn'
        elements:
          type: array
          items:
            type: string
        scans:
          type: array
          items:
            $ref: '#/components/schemas/FileUrn'
        tolerance:
          type: number
    File:
      type: object
      x-tags:
        - File
      oneOf:
        - $ref: '#/components/schemas/Archive'
        - $ref: '#/components/schemas/Document'
        - $ref: '#/components/schemas/GeoImage'
        - $ref: '#/components/schemas/Media'
        - $ref: '#/components/schemas/MiscFile'
        - $ref: '#/components/schemas/Model'
        - $ref: '#/components/schemas/ModelReport'
        - $ref: '#/components/schemas/Scan'
        - $ref: '#/components/schemas/SiteMap'
      discriminator:
        propertyName: type
        mapping:
          archive: '#/components/schemas/Archive'
          document: '#/components/schemas/Document'
          geoImage: '#/components/schemas/GeoImage'
          media: '#/components/schemas/Media'
          miscfile: '#/components/schemas/MiscFile'
          model: '#/components/schemas/Model'
          modelReport: '#/components/schemas/ModelReport'
          scan: '#/components/schemas/Scan'
          sitemap: '#/components/schemas/SiteMap'
    fileUpdate:
      type: object
      required:
        - name
      properties:
        name:
          type: string
          minimum: 1
          maximum: 255
          description: New name for the file
    filesTransformationUpdate:
      type: array
      items:
        allOf:
          - $ref: '#/components/schemas/FileTransformable'
          - type: object
            required:
              - id
            properties:
              id:
                $ref: '#/components/schemas/FileUrn'
    ShareLinkUrn:
      type: string
      description: |
        "urn:cintoo:sharelink:" followed by a string
    ShareLinkOptions:
      type: object
      required:
        - workzones
        - notes
        - issues
        - measurements
        - crops
        - tags
      x-tags:
        - Share Link
      properties:
        workzones:
          type: boolean
          description: when enabled, multiple work zones are shared if specified in `sharedWorkzonesIds`
        notes:
          type: boolean
          description: when enabled, annotations of type NOTE are visible using the share link
        issues:
          type: boolean
          description: when enabled, annotations of type ISSUE or RESOLVED are visible using the share link
        measurements:
          type: boolean
          description: when enabled, measurements are visible using the share link
        crops:
          type: boolean
          description: when enabled, crops are visible using the share link
        tags:
          type: boolean
          description: when enabled, tags are visible using the share link
    SavedViewData:
      oneOf:
        - type: object
          title: legacy
          required:
            - type
          properties:
            type:
              type: string
              enum:
                - legacy
              description: internal legacy representation, do not use (subject to changes and deprecation)
        - type: object
          title: others
          required:
            - type
          properties:
            type:
              type: string
              description: structure will be defined in the future
          additionalProperties: true
    ShareLink:
      type: object
      required:
        - id
        - type
        - name
        - link
        - workzoneId
        - sharedWorkzonesIds
        - createdBy
        - createdAt
        - expiresAt
        - passwordProtected
        - membershipRequired
        - shareOptions
        - logoBlob
        - savedView
      x-tags:
        - Share Link
      properties:
        id:
          readOnly: true
          $ref: '#/components/schemas/ShareLinkUrn'
        type:
          type: string
          enum:
            - sharelink
        name:
          type: string
          nullable: true
        link:
          type: string
        workzoneId:
          $ref: '#/components/schemas/WorkzoneUrn'
        sharedWorkzonesIds:
          type: array
          items:
            $ref: '#/components/schemas/WorkzoneUrn'
          description: list of additional shared work zones (only if `workzones` is enabled in the `shareOptions`)
        createdBy:
          $ref: '#/components/schemas/UserUrn'
        createdAt:
          $ref: '#/components/schemas/DateTime'
        expiresAt:
          allOf:
            - $ref: '#/components/schemas/DateTime'
          type: string
          nullable: true
        passwordProtected:
          type: boolean
          description: Indicates if the share link will require a password to be supplied
        membershipRequired:
          type: boolean
          description: Indicates if the user must be already logged in to use the share link
        shareOptions:
          $ref: '#/components/schemas/ShareLinkOptions'
        logoBlob:
          type: string
          nullable: true
        savedView:
          $ref: '#/components/schemas/SavedViewData'
    ShareLinkPassword:
      type: string
      description: |
        Password must be at least 12 characters long and contain at least 3 of the following:
        uppercase, lowercase, digit, special characters, or be at least 24 characters long with no
        character set constraints
    shareLinkCreate:
      type: object
      required:
        - name
        - workzoneId
        - membershipRequired
        - shareOptions
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
        workzoneId:
          $ref: '#/components/schemas/WorkzoneUrn'
        sharedWorkzonesIds:
          type: array
          nullable: true
          items:
            $ref: '#/components/schemas/WorkzoneUrn'
          description: |
            Additional work zones to share.
            This is taken into account only if `workzones` is enabled in `shareOptions`.
        password:
          allOf:
            - $ref: '#/components/schemas/ShareLinkPassword'
          type: string
          nullable: true
        expiresAt:
          allOf:
            - $ref: '#/components/schemas/DateTime'
          type: string
          nullable: true
        logoBlob:
          type: string
          nullable: true
        membershipRequired:
          type: boolean
          description: Indicates if the user must be already logged in to use the share link
        shareOptions:
          $ref: '#/components/schemas/ShareLinkOptions'
        savedView:
          allOf:
            - $ref: '#/components/schemas/SavedViewData'
          type: object
          nullable: true
    shareLinkUpdate:
      type: object
      required:
        - name
        - sharedWorkzonesIds
        - password
        - expiresAt
        - logoBlob
        - membershipRequired
        - shareOptions
        - savedView
      properties:
        name:
          type: string
          minLength: 1
          maxLength: 255
        sharedWorkzonesIds:
          type: array
          nullable: true
          items:
            $ref: '#/components/schemas/WorkzoneUrn'
          description: |
            Additional work zones to share.
            This is taken into account only if `workzones` is enabled in `shareOptions`.
        password:
          allOf:
            - $ref: '#/components/schemas/ShareLinkPassword'
          type: string
          nullable: true
        expiresAt:
          allOf:
            - $ref: '#/components/schemas/DateTime'
          type: string
          nullable: true
        logoBlob:
          type: string
          nullable: true
        membershipRequired:
          type: boolean
          description: Indicates if the user must be already logged in to use the share link
        shareOptions:
          $ref: '#/components/schemas/ShareLinkOptions'
        savedView:
          allOf:
            - $ref: '#/components/schemas/SavedViewData'
          type: object
          nullable: true
    bulk-files:
      type: object
      required:
        - name
        - extension
        - size
        - md5
      properties:
        name:
          type: string
          minimum: 1
          maximum: 255
        extension:
          type: string
        size:
          type: integer
        md5:
          type: string
    ProjectRef:
      oneOf:
        - $ref: '#/components/schemas/ProjectUrn'
        - $ref: '#/components/schemas/ProjectId'
    AnnotationUrn:
      type: string
      description: |
        "urn:cintoo:annotation:" followed by an UUIDv4
    Point3D:
      type: object
      required:
        - x
        - 'y'
        - z
      properties:
        x:
          type: number
          format: double
        'y':
          type: number
          format: double
        z:
          type: number
          format: double
    AnnotationCommentUrn:
      type: string
      description: |
        "urn:cintoo:annotation-comment:" followed by an UUIDv4
    AnnotationCommentAttachmentUrn:
      type: string
      description: |
        "urn:cintoo:annotation-comment-attachment:" followed by an UUIDv4
    FileAttribute:
      type: object
      properties:
        id:
          $ref: '#/components/schemas/FileUrn'
        size:
          type: integer
        name:
          type: string
        formatId:
          $ref: '#/components/schemas/FormatUrn'
        blob:
          $ref: '#/components/schemas/BlobName'
    AnnotationCommentAttachment:
      type: object
      required:
        - id
        - type
      x-tags:
        - Annotation
      properties:
        id:
          readOnly: true
          $ref: '#/components/schemas/AnnotationCommentAttachmentUrn'
        type:
          type: string
          enum:
            - annotation-comment-attachment
        attachedTo:
          $ref: '#/components/schemas/AnnotationCommentUrn'
        file:
          $ref: '#/components/schemas/FileAttribute'
    AnnotationComment:
      type: object
      required:
        - id
        - type
        - modificationInfo
      x-tags:
        - Annotation
      properties:
        id:
          readOnly: true
          $ref: '#/components/schemas/AnnotationCommentUrn'
        type:
          type: string
          enum:
            - annotation-comment
        modificationInfo:
          $ref: '#/components/schemas/ModificationInfo'
        attachedTo:
          $ref: '#/components/schemas/AnnotationUrn'
        description:
          type: string
        descriptionText:
          type: string
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/AnnotationCommentAttachment'
    AnnotationAttachmentUrn:
      type: string
      description: |
        "urn:cintoo:annotation-attachment:" followed by an UUIDv4
    AnnotationAttachment:
      type: object
      required:
        - id
        - type
      x-tags:
        - Annotation
      properties:
        id:
          readOnly: true
          $ref: '#/components/schemas/AnnotationAttachmentUrn'
        type:
          type: string
          enum:
            - annotation-attachment
        attachedTo:
          $ref: '#/components/schemas/AnnotationUrn'
        file:
          $ref: '#/components/schemas/FileAttribute'
    AnnotationIntegration:
      type: object
      required:
        - type
      x-tags:
        - Annotation
      properties:
        type:
          type: string
          enum:
            - AUTODESK
            - NEWFORMA_KONEKT
            - PROCORE
        url:
          type: string
          nullable: true
    Annotation:
      type: object
      required:
        - id
        - type
        - workzoneId
        - modificationInfo
        - savedView
      x-tags:
        - Annotation
      properties:
        id:
          readOnly: true
          $ref: '#/components/schemas/AnnotationUrn'
        type:
          type: string
          enum:
            - annotation
        workzoneId:
          $ref: '#/components/schemas/WorkzoneUrn'
        modificationInfo:
          $ref: '#/components/schemas/ModificationInfo'
        annotationType:
          type: string
          enum:
            - Private note
            - Note
            - Issue
            - Resolved
        title:
          type: string
        description:
          type: string
        descriptionText:
          type: string
        assignedTo:
          type: array
          items:
            $ref: '#/components/schemas/UserUrn'
        dueDate:
          $ref: '#/components/schemas/Date'
        priority:
          type: integer
        number:
          type: integer
        position:
          $ref: '#/components/schemas/Point3D'
        normal:
          $ref: '#/components/schemas/Point3D'
        imgBlobName:
          type: string
        savedView:
          $ref: '#/components/schemas/SavedViewData'
        modelElementGUIDs:
          type: array
          nullable: true
          items:
            type: string
        comments:
          type: array
          items:
            $ref: '#/components/schemas/AnnotationComment'
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/AnnotationAttachment'
        labels:
          type: array
          items:
            type: string
        integrations:
          type: array
          items:
            $ref: '#/components/schemas/AnnotationIntegration'
    annotationCreate:
      type: object
      required:
        - workzoneId
        - annotationType
        - title
        - position
        - savedView
      properties:
        workzoneId:
          $ref: '#/components/schemas/WorkzoneUrn'
        annotationType:
          type: string
          enum:
            - Private note
            - Note
            - Issue
            - Resolved
        title:
          type: string
          minLength: 1
          maxLength: 255
        description:
          type: string
          nullable: true
        assignedTo:
          type: array
          items:
            $ref: '#/components/schemas/UserUrn'
        dueDate:
          type: string
          format: date
        priority:
          type: integer
          enum:
            - 0
            - 1
            - 5
            - 8
            - 10
        imgBlobName:
          type: string
        position:
          $ref: '#/components/schemas/Point3D'
        normal:
          $ref: '#/components/schemas/Point3D'
        savedView:
          $ref: '#/components/schemas/SavedViewData'
        modelElementGUIDs:
          type: array
          nullable: true
          items:
            type: string
        labels:
          type: array
          items:
            type: string
        attachments:
          type: array
          items:
            type: string
            example: '{blobName}'
          description: blob names
    AnnotationId:
      allOf:
        - $ref: '#/components/schemas/Uuid'
      example: 8196e320-8a4d-4d29-b0a9-077cd7ed3368
    annotationUpdate:
      type: object
      required:
        - annotationType
        - title
        - position
      properties:
        annotationType:
          type: string
          enum:
            - Private note
            - Note
            - Issue
            - Resolved
        title:
          type: string
          minLength: 1
          maxLength: 255
        description:
          type: string
          nullable: true
        assignedTo:
          type: array
          items:
            $ref: '#/components/schemas/UserUrn'
        dueDate:
          type: string
          format: date
        priority:
          type: integer
          enum:
            - 0
            - 1
            - 5
            - 8
            - 10
        imgBlobName:
          type: string
        position:
          $ref: '#/components/schemas/Point3D'
        normal:
          $ref: '#/components/schemas/Point3D'
        savedView:
          $ref: '#/components/schemas/SavedViewData'
        modelElementGUIDs:
          type: array
          nullable: true
          items:
            type: string
        labels:
          type: array
          items:
            type: string
        attachments:
          type: array
          items:
            type: string
            example: '{blobName}'
          description: blob names
    annotationCommentCreate:
      type: object
      required:
        - description
      properties:
        description:
          type: string
          minLength: 1
        attachments:
          type: array
          items:
            type: string
            example: '{blobName}'
          description: blob names
    annotationCreateIntegrationLinks:
      type: object
      required:
        - integrationTypes
      properties:
        annotations:
          type: array
          description: If omitted or null, all annotations of the project will be processed.
          nullable: true
          items:
            $ref: '#/components/schemas/AnnotationUrn'
        integrationTypes:
          type: array
          items:
            type: string
            enum:
              - AUTODESK
              - NEWFORMA_KONEKT
              - PROCORE
    AnnotationBulkCreateIntegrationLinksResponse:
      type: object
      required:
        - created
      properties:
        created:
          type: object
          description: Each key is an annotation URN, values are integration types initialized for the annotation
          additionalProperties:
            x-additionalPropertiesName: urn:cintoo:annotation:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
            type: array
            items:
              type: string
              enum:
                - AUTODESK
                - NEWFORMA_KONEKT
                - PROCORE
          example:
            urn:cintoo:annotation:956b7fb8-ad08-4f63-bed2-f958f0bf2e2c:
              - AUTODESK
              - PROCORE
            urn:cintoo:annotation:2d90addd-e3fb-4a0c-b54a-af09e17687e2:
              - NEWFORMA_KONEKT
    ProjectIntegrationKonekt:
      type: object
      x-tags:
        - Konekt
      required:
        - projectId
        - hubId
        - konektId
        - options
        - link
        - createdAt
        - createdBy
        - updatedAt
        - updatedBy
      properties:
        projectId:
          $ref: '#/components/schemas/ProjectUrn'
        hubId:
          type: string
          description: id of the hub on Konekt
        konektId:
          type: number
          description: id of the project on Konekt
        options:
          $ref: '#/components/schemas/ProjectIntegrationNewformaKonektOptions'
        link:
          type: string
        createdAt:
          $ref: '#/components/schemas/DateTime'
        createdBy:
          $ref: '#/components/schemas/UserUrn'
        updatedAt:
          $ref: '#/components/schemas/DateTime'
        updatedBy:
          $ref: '#/components/schemas/UserUrn'
    ProjectIntegrationAutodesk:
      type: object
      x-tags:
        - Autodesk
      required:
        - projectId
        - autodeskHubId
        - autodeskProjectId
        - link
        - createdAt
        - createdBy
        - updatedAt
        - updatedBy
      properties:
        projectId:
          $ref: '#/components/schemas/ProjectUrn'
        autodeskHubId:
          type: string
        autodeskProjectId:
          type: string
        link:
          type: string
        createdAt:
          $ref: '#/components/schemas/DateTime'
        createdBy:
          $ref: '#/components/schemas/UserUrn'
        updatedAt:
          $ref: '#/components/schemas/DateTime'
        updatedBy:
          $ref: '#/components/schemas/UserUrn'
    ProjectIntegrations:
      type: object
      properties:
        konekt:
          allOf:
            - $ref: '#/components/schemas/ProjectIntegrationKonekt'
          type: object
          nullable: true
        autodesk:
          allOf:
            - $ref: '#/components/schemas/ProjectIntegrationAutodesk'
          type: object
          nullable: true
    projectIntegrationKonektUpdate:
      type: object
      required:
        - hubId
        - konektProjectId
        - options
      properties:
        hubId:
          type: string
          description: id of the hub on Konekt
        konektProjectId:
          type: number
          description: id of the project on Konekt
        options:
          $ref: '#/components/schemas/ProjectIntegrationNewformaKonektOptions'
    projectIntegrationKonektCreate:
      type: object
      required:
        - hubId
        - konektProjectId
        - options
      properties:
        hubId:
          type: string
          description: id of the hub on Konekt
        konektProjectId:
          type: number
          description: id of the project on Konekt
        options:
          $ref: '#/components/schemas/ProjectIntegrationNewformaKonektOptions'
    projectIntegrationAutodeskUpdate:
      type: object
      required:
        - autodeskHubId
        - autodeskProjectId
      properties:
        autodeskHubId:
          type: string
        autodeskProjectId:
          type: string
    projectIntegrationAutodeskCreate:
      type: object
      required:
        - autodeskHubId
        - autodeskProjectId
      properties:
        autodeskHubId:
          type: string
        autodeskProjectId:
          type: string
    CropUrn:
      type: string
      description: |
        "urn:cintoo:crop:" followed by an UUIDv4
    Crop:
      type: object
      required:
        - id
        - workzoneId
        - name
        - crop
        - actualCrop
        - rotation
        - cameraType
        - cameraState
        - scan
        - createdBy
        - createdAt
        - updatedBy
        - updatedAt
      x-tags:
        - Crop
      properties:
        id:
          readOnly: true
          $ref: '#/components/schemas/CropUrn'
        workzoneId:
          $ref: '#/components/schemas/WorkzoneUrn'
        name:
          type: string
        crop:
          type: string
          description: Description of the plans defining the bounding box of the crop in the scene
        actualCrop:
          type: string
          description: Description of the plans defining the bounding box of the crop related to the camera
        rotation:
          type: string
          description: Description about the rotation of the crop inside the camera
        layers:
          type: string
          nullable: true
          description: Scans related to the crop
        cameraType:
          type: string
          description: Details about the camera (overview, ground, scan, arcrotate)
        cameraState:
          type: string
          description: Description of source and direction of the camera of the scene
        scan:
          type: string
          description: root scan for the crop
        thumbnailBlobName:
          type: string
          nullable: true
          allOf:
            - $ref: '#/components/schemas/BlobName'
        thumbnailUrl:
          type: string
          nullable: true
          allOf:
            - $ref: '#/components/schemas/Url'
        oview:
          type: number
          nullable: true
        settings:
          type: string
          nullable: true
        createdBy:
          $ref: '#/components/schemas/UserUrn'
        createdAt:
          $ref: '#/components/schemas/DateTime'
        updatedBy:
          $ref: '#/components/schemas/UserUrn'
        updatedAt:
          $ref: '#/components/schemas/DateTime'
    cropCreate:
      type: object
      required:
        - workzoneId
        - name
        - crop
        - actualCrop
        - rotation
        - cameraType
        - cameraState
        - scan
      properties:
        workzoneId:
          $ref: '#/components/schemas/WorkzoneRef'
        name:
          type: string
        crop:
          type: string
          description: Description of the plans defining the bounding box of the crop in the scene
        actualCrop:
          type: string
          description: Description of the plans defining the bounding box of the crop related to the camera
        rotation:
          type: string
          description: Description about the rotation of the crop inside the camera
        layers:
          type: string
          nullable: true
          description: Scans related to the crop
        cameraType:
          type: string
          description: Details about the camera (overview, ground, scan, arcrotate)
        cameraState:
          type: string
          description: Description of source and direction of the camera of the scene
        scan:
          type: string
          description: root scan for the crop
        thumbnailBlobName:
          type: string
          nullable: true
          allOf:
            - $ref: '#/components/schemas/BlobName'
        oview:
          type: number
          nullable: true
        settings:
          type: string
          nullable: true
    CropId:
      allOf:
        - $ref: '#/components/schemas/Uuid'
      example: 8196e320-8a4d-4d29-b0a9-077cd7ed3368
    cropUpdate:
      type: object
      required:
        - workzoneId
        - name
        - crop
        - actualCrop
        - rotation
        - cameraType
        - cameraState
        - scan
      properties:
        workzoneId:
          $ref: '#/components/schemas/WorkzoneRef'
        name:
          type: string
        crop:
          type: string
          description: Description of the plans defining the bounding box of the crop in the scene
        actualCrop:
          type: string
          description: Description of the plans defining the bounding box of the crop related to the camera
        rotation:
          type: string
          description: Description about the rotation of the crop inside the camera
        layers:
          type: string
          nullable: true
          description: Scans related to the crop
        cameraType:
          type: string
          description: Details about the camera (overview, ground, scan, arcrotate)
        cameraState:
          type: string
          description: Description of source and direction of the camera of the scene
        scan:
          type: string
          description: root scan for the crop
        thumbnailBlobName:
          type: string
          nullable: true
          allOf:
            - $ref: '#/components/schemas/BlobName'
        oview:
          type: number
          nullable: true
        settings:
          type: string
          nullable: true
    MeasurementUrn:
      type: string
      description: |
        "urn:cintoo:measurement:" followed by an UUIDv4
    Vector3:
      type: array
      minItems: 3
      maxItems: 3
      items:
        type: number
    Vector4:
      type: array
      minItems: 4
      maxItems: 4
      items:
        type: number
    TagDataCameraState:
      title: Camera State
      type: object
      properties:
        position:
          $ref: '#/components/schemas/Vector3'
        target:
          $ref: '#/components/schemas/Vector3'
        rotation:
          $ref: '#/components/schemas/Vector3'
        overviewAngle:
          type: string
          maxLength: 10
        viewDirection:
          type: string
          maxLength: 10
        rotationQuaternion:
          $ref: '#/components/schemas/Vector4'
        fov:
          type: number
        overviewScale:
          type: number
        ortho:
          type: boolean
        camType:
          type: string
          maxLength: 10
      additionalProperties: false
    Measurement:
      type: object
      required:
        - id
        - type
        - workzoneId
        - name
      x-tags:
        - Measurement
      properties:
        id:
          type: string
          $ref: '#/components/schemas/MeasurementUrn'
        type:
          type: string
          enum:
            - measurement
        workzoneId:
          type: string
          $ref: '#/components/schemas/WorkzoneUrn'
        name:
          description: The name of the measurement
          type: string
        startPoint:
          type: object
          nullable: true
          allOf:
            - $ref: '#/components/schemas/Point3D'
        endPoint:
          type: object
          nullable: true
          allOf:
            - $ref: '#/components/schemas/Point3D'
        length:
          type: number
          nullable: true
          format: double
        height:
          type: number
          nullable: true
          format: double
        width:
          type: number
          nullable: true
          format: double
        thumbnailBlobName:
          type: string
          nullable: true
          allOf:
            - $ref: '#/components/schemas/BlobName'
        cameraState:
          type: object
          nullable: true
          allOf:
            - $ref: '#/components/schemas/TagDataCameraState'
        scanId:
          description: Reference to the scan which has been used for this measurement
          type: string
          nullable: true
          allOf:
            - $ref: '#/components/schemas/FileUrn'
        createdAt:
          type: string
          nullable: true
          allOf:
            - $ref: '#/components/schemas/DateTime'
        createdBy:
          type: string
          nullable: true
          allOf:
            - $ref: '#/components/schemas/UserUrn'
        isDeleted:
          type: boolean
        deletedAt:
          type: string
          nullable: true
          allOf:
            - $ref: '#/components/schemas/DateTime'
    TagListUrn:
      type: string
      description: |
        "urn:cintoo:tag-list:" followed by an UUIDv4
    TagListMetadataDefinition:
      type: object
      required:
        - id
        - name
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 256
        name:
          type: string
          minLength: 1
          maxLength: 100
          pattern: ^[^\s](.*[^\s])?$
          description: |
            must not start or end with a space, forbidden keywords are:
            - "id"
            - "description"
            - "cameraState"
            - "model"
            - "x"
            - "y"
            - "z"
            - "size"
            - "xMin"
            - "xMax"
            - "yMin"
            - "yMax"
            - "zMin"
            - "zMax"
            - "scanUuids"
            - "subId"
            - "nature"
            - "confidence"
            - "needValidation"
          not:
            enum:
              - id
              - description
              - cameraState
              - model
              - x
              - 'y'
              - z
              - size
              - xMin
              - xMax
              - yMin
              - yMax
              - zMin
              - zMax
              - scanUuids
              - subId
              - nature
              - confidence
              - needValidation
    TagListAttachment:
      type: object
      required:
        - id
        - name
        - pages
      properties:
        id:
          type: string
          minLength: 1
          maxLength: 256
        name:
          type: string
          minLength: 1
          maxLength: 256
        blob:
          type: string
        pages:
          type: array
          items:
            type: object
            required:
              - id
              - index
            properties:
              id:
                type: string
                minLength: 1
                maxLength: 256
              index:
                type: integer
              scale:
                type: number
              width:
                type: number
              height:
                type: number
              name:
                type: string
                minLength: 1
                maxLength: 256
              hidden:
                type: boolean
    TagList:
      type: object
      x-tags:
        - Tag List
      required:
        - id
        - type
        - workzoneId
        - tagListType
        - name
        - description
        - color
        - metadataDefinition
        - pending
        - createdBy
        - createdAt
        - updatedBy
        - updatedAt
        - tagsVersion
      properties:
        id:
          $ref: '#/components/schemas/TagListUrn'
        type:
          type: string
          enum:
            - tag-list
        workzoneId:
          $ref: '#/components/schemas/WorkzoneUrn'
        tagListType:
          type: string
        name:
          type: string
          minLength: 1
          maxLength: 256
        description:
          type: string
          maxLength: 1000
        color:
          type: string
          maxLength: 20
        metadataDefinition:
          type: array
          minItems: 0
          maxItems: 50
          items:
            $ref: '#/components/schemas/TagListMetadataDefinition'
          description: |
            Defines for each metadata id a displayable name.
            Note that tags inside the list may have metadata not defined in the list, but only those defined on the
            list will be displayed on the webapp.
        attachments:
          type: array
          items:
            $ref: '#/components/schemas/TagListAttachment'
        pending:
          type: boolean
        createdBy:
          $ref: '#/components/schemas/UserUrn'
        createdAt:
          $ref: '#/components/schemas/DateTime'
        updatedBy:
          $ref: '#/components/schemas/UserUrn'
        updatedAt:
          $ref: '#/components/schemas/DateTime'
        tagsVersion:
          type: integer
    tagListCreate:
      type: object
      required:
        - workzoneId
        - tagListType
        - name
      properties:
        workzoneId:
          $ref: '#/components/schemas/WorkzoneRef'
        tagListType:
          type: string
          minLength: 1
          maxLength: 36
          description: |
            The tag list type determines how to display and handle it on the webapp. Currently only `2d` and `3d` are
            supported by the webapp.
        name:
          type: string
          minLength: 1
          maxLength: 256
        description:
          type: string
          maxLength: 1000
          nullable: true
        color:
          type: string
          maxLength: 20
          nullable: true
        metadataDefinition:
          type: array
          nullable: true
          minItems: 0
          maxItems: 50
          items:
            $ref: '#/components/schemas/TagListMetadataDefinition'
        attachments:
          type: array
          nullable: true
          items:
            $ref: '#/components/schemas/TagListAttachment'
    TagListId:
      allOf:
        - $ref: '#/components/schemas/Uuid'
      example: 8196e320-8a4d-4d29-b0a9-077cd7ed3368
    TagListRef:
      oneOf:
        - $ref: '#/components/schemas/TagListUrn'
        - $ref: '#/components/schemas/TagListId'
    tagListUpdate:
      type: object
      required:
        - workzoneId
        - tagListType
        - name
      properties:
        workzoneId:
          $ref: '#/components/schemas/WorkzoneRef'
        tagListType:
          type: string
          minLength: 1
          maxLength: 36
          description: |
            The tag list type determines how to display and handle it on the webapp. Currently only `2d` and `3d` are
            supported by the webapp.
        name:
          type: string
          minLength: 1
          maxLength: 256
        description:
          type: string
          maxLength: 1000
          nullable: true
        color:
          type: string
          maxLength: 20
          nullable: true
        metadataDefinition:
          type: array
          nullable: true
          minItems: 0
          maxItems: 50
          items:
            $ref: '#/components/schemas/TagListMetadataDefinition'
        attachments:
          type: array
          nullable: true
          items:
            $ref: '#/components/schemas/TagListAttachment'
    FileRef:
      oneOf:
        - $ref: '#/components/schemas/FileUrn'
        - $ref: '#/components/schemas/FileId'
    TagUrn:
      type: string
      description: |
        "urn:cintoo:tag:" followed by an UUIDv4
    TagBoundingBox:
      title: Bounding Box
      description: |
        [xMin, yMin, zMin, xMax, yMax, zMax]:
        * (xMin, yMin, zMin) min position
        * (xMax, yMax, zMax) max position
      type: array
      minItems: 6
      maxItems: 6
      items:
        type: number
        format: float
    TagOrientedBoundingBox:
      title: Oriented Bounding Box
      description: |
        [xPos, yPos, zPos, xQuat, yQuat, zQuat, wQuat, xScale, yScale, zScale]:
        * (xPos, yPos, zPos) position of the center of the box, in meters
        * (xQuat, yQuat, zQuat, wQuat) rotation (as a quaternion) of the box, using the center of the box as a pivot
        * (xScale, yScale, zScale) scale of the box, in meters
      type: array
      minItems: 10
      maxItems: 10
      items:
        type: number
        format: float
    TagDataModelAxisAlignedList:
      title: Tag Data Model Using Bounding Box List
      type: object
      required:
        - type
        - list
      properties:
        type:
          type: string
          enum:
            - AxisAlignedList
        list:
          type: array
          items:
            $ref: '#/components/schemas/TagBoundingBox'
          minItems: 2
          maxItems: 1000
      additionalProperties: false
    TagDataModelBinarizedAxisAlignedList:
      title: Tag Data Model Using Binary Bounding Box List
      type: object
      required:
        - type
        - binary
      properties:
        type:
          type: string
          enum:
            - BinarizedAxisAlignedList
        binary:
          type: string
          maxLength: 32000
      additionalProperties: false
    TagSharedOrientedBoundingBox:
      title: Tag Oriented Bounding Box List With Shared Orientation
      type: array
      minItems: 16
      maxItems: 505
      items:
        type: number
    TagDataModelOrientedList:
      title: Tag Data Model Using Oriented Bounding Box List
      type: object
      required:
        - type
      properties:
        type:
          type: string
          enum:
            - OrientedList
        list:
          type: array
          maxItems: 500
          items:
            $ref: '#/components/schemas/TagBoundingBox'
        groupedOBBList:
          type: array
          maxItems: 500
          items:
            $ref: '#/components/schemas/TagSharedOrientedBoundingBox'
        orientedBoundingBoxList:
          type: array
          maxItems: 500
          items:
            $ref: '#/components/schemas/TagOrientedBoundingBox'
    TagDataModel2DKonva:
      title: Tag 2d Data Model Using Konva Shapes
      type: object
      required:
        - type
        - data
      properties:
        type:
          type: string
          enum:
            - konva
        data:
          type: string
    TagDataNature:
      title: Tag Data Nature
      type: string
      enum:
        - AI
        - file
        - manual
        - model
    TagDataCustomFields:
      title: Tag Data Custom Fields
      type: array
      minItems: 0
      maxItems: 10
      items:
        type: object
        properties:
          name:
            type: string
            maxLength: 50
          value:
            type: string
            maxLength: 1000
        required:
          - name
          - value
        additionalProperties: false
    TagDataMetadata:
      title: Tag Data Metadata
      type: array
      minItems: 0
      maxItems: 50
      items:
        type: object
        properties:
          id:
            type: string
            minLength: 1
            maxLength: 256
            description: |
              Id of the metadata.
              It is not mandatory that the id exist on the tag list, however only those defined on the tag list
              will be displayed on the webapp.
          value:
            type: string
            maxLength: 1000
        required:
          - id
          - value
        additionalProperties: false
    TagData:
      title: Tag Data
      description: For more information about how the geometry of a tag is represented, see the dedicated section [Understand Tag Data](#tag/understand_tag_data)
      type: object
      required:
        - id
      properties:
        id:
          description: Tag external ID, reference of the tag. Displayed in TagList
          type: string
          minLength: 1
          maxLength: 256
        subId:
          type: string
          minLength: 1
          maxLength: 50
        uuid:
          $ref: '#/components/schemas/Uuid'
        name:
          type: string
          minLength: 1
          maxLength: 50
        pageId:
          type: string
          minLength: 1
          maxLength: 256
        pageLinkId:
          type: string
          minLength: 1
          maxLength: 256
        tagLinkIds:
          type: array
          items:
            type: string
            minLength: 1
            maxLength: 256
        boundingBox:
          $ref: '#/components/schemas/TagBoundingBox'
        orientedBoundingBox:
          $ref: '#/components/schemas/TagOrientedBoundingBox'
        model:
          anyOf:
            - $ref: '#/components/schemas/TagDataModelAxisAlignedList'
            - $ref: '#/components/schemas/TagDataModelBinarizedAxisAlignedList'
            - $ref: '#/components/schemas/TagDataModelOrientedList'
            - $ref: '#/components/schemas/TagDataModel2DKonva'
        class:
          type: string
          maxLength: 50
          example: pump
        cameraState:
          $ref: '#/components/schemas/TagDataCameraState'
        unit:
          type: number
        functionalSystem:
          type: string
          maxLength: 50
        description:
          type: string
          maxLength: 1000
        url:
          type: string
          maxLength: 1000
          format: uri
        confidence:
          type: number
          format: float
        scanUuids:
          type: array
          items:
            $ref: '#/components/schemas/Uuid'
        needValidation:
          type: boolean
        nature:
          $ref: '#/components/schemas/TagDataNature'
        customFields:
          $ref: '#/components/schemas/TagDataCustomFields'
        metadata:
          $ref: '#/components/schemas/TagDataMetadata'
      additionalProperties: false
    Tag:
      type: object
      x-tags:
        - Tag
      required:
        - id
        - type
        - tagListId
        - tagListType
        - createdBy
        - createdAt
        - updatedBy
        - updatedAt
        - isDeleted
        - deletedBy
        - deletedAt
        - tagsVersion
        - data
      properties:
        id:
          $ref: '#/components/schemas/TagUrn'
        type:
          type: string
          enum:
            - tag
        tagListId:
          $ref: '#/components/schemas/TagListUrn'
        tagListType:
          type: string
        createdBy:
          $ref: '#/components/schemas/UserUrn'
        createdAt:
          $ref: '#/components/schemas/DateTime'
        updatedBy:
          $ref: '#/components/schemas/UserUrn'
        updatedAt:
          $ref: '#/components/schemas/DateTime'
        isDeleted:
          type: boolean
        deletedBy:
          allOf:
            - $ref: '#/components/schemas/UserUrn'
          type: string
          nullable: true
        deletedAt:
          allOf:
            - $ref: '#/components/schemas/DateTime'
          type: string
          nullable: true
        tagsVersion:
          type: integer
        data:
          $ref: '#/components/schemas/TagData'
    TagId:
      allOf:
        - $ref: '#/components/schemas/Uuid'
      example: 8196e320-8a4d-4d29-b0a9-077cd7ed3368
    TagRef:
      oneOf:
        - $ref: '#/components/schemas/TagUrn'
        - $ref: '#/components/schemas/TagId'
    tagsBulkRequest:
      type: object
      properties:
        create:
          type: array
          items:
            type: object
            required:
              - data
            properties:
              data:
                $ref: '#/components/schemas/TagData'
        update:
          type: array
          items:
            type: object
            required:
              - id
              - data
            properties:
              id:
                $ref: '#/components/schemas/TagRef'
              data:
                $ref: '#/components/schemas/TagData'
        delete:
          type: array
          items:
            $ref: '#/components/schemas/TagRef'
    tagsBulkResponse:
      type: object
      required:
        - tagsVersion
        - created
        - updated
        - deleted
      properties:
        tagsVersion:
          type: integer
        created:
          type: array
          items:
            $ref: '#/components/schemas/Tag'
        updated:
          type: array
          items:
            $ref: '#/components/schemas/Tag'
        deleted:
          type: array
          items:
            $ref: '#/components/schemas/TagUrn'
    tagsMoveBulkRequest:
      type: array
      items:
        type: object
        required:
          - sourceTagListId
          - tagsIds
        properties:
          sourceTagListId:
            $ref: '#/components/schemas/TagListRef'
          tagsIds:
            type: array
            items:
              $ref: '#/components/schemas/TagRef'
          metadataIdMapping:
            type: array
            nullable: true
            items:
              type: object
              required:
                - sourceId
                - targetId
              properties:
                sourceId:
                  type: string
                  minLength: 1
                  maxLength: 256
                  description: metadata id in the source tag list
                targetId:
                  type: string
                  minLength: 1
                  maxLength: 256
                  description: metadata id in the target tag list
    tagsMoveBulkResponse:
      type: array
      items:
        type: object
        required:
          - tagList
          - updatedTags
        properties:
          tagList:
            $ref: '#/components/schemas/TagList'
          updatedTags:
            type: array
            items:
              $ref: '#/components/schemas/Tag'
    Permission:
      type: object
      required:
        - name
      x-tags:
        - Permissions
      properties:
        name:
          type: string
    Id:
      type: number
      readOnly: true
    FormatId:
      allOf:
        - $ref: '#/components/schemas/Id'
      example: 5
    FileFormat:
      type: object
      x-tags:
        - File
      required:
        - id
        - name
        - description
        - extension
        - icon
        - canBeUploaded
        - shareUrlType
        - categoryName
        - categoryDescription
        - categoryType
      properties:
        id:
          $ref: '#/components/schemas/FormatId'
        name:
          type: string
        description:
          type: string
        extension:
          type: string
        icon:
          type: string
        canBeUploaded:
          type: boolean
        shareUrlType:
          type: string
        categoryName:
          type: string
        categoryDescription:
          type: string
        categoryType:
          type: string
    SpatialReferenceValue:
      type: object
      required:
        - authName
        - code
        - deprecated
        - name
      properties:
        authName:
          type: string
          description: The authority defining the used code (IGNF, NKG, OGC, IAU_2015, EPSG, ESRI)
        code:
          type: string
          description: The code of the geographic reference to be used for this project
        deprecated:
          type: boolean
          description: If true, the spatialReference cannot be used to define any project anymore (only available for already settled projects)
        name:
          type: string
          description: Description about the location of the SpatialReference
    AIDetectionClass:
      type: object
      required:
        - name
        - category
      properties:
        name:
          type: string
          description: Identifier of the AI detection class
        category:
          type: string
          description: Category associated with the class
    AutodeskConnection:
      type: object
      x-tags:
        - Autodesk
      required:
        - hubs
      properties:
        email:
          type: string
          nullable: true
        userName:
          type: string
          nullable: true
        firstName:
          type: string
          nullable: true
        lastName:
          type: string
          nullable: true
        hubs:
          type: array
          items:
            type: object
            required:
              - autodeskId
              - name
              - projects
            properties:
              autodeskId:
                type: string
              name:
                type: string
              projects:
                type: array
                items:
                  type: object
                  required:
                    - autodeskId
                    - name
                  properties:
                    autodeskId:
                      type: string
                    name:
                      type: string
    TenantSettingKey:
      type: string
      description: |
        Available tenant setting keys:
        - `mfaEnabled` (boolean): Whether multi-factor authentication is enabled for the tenant
      enum:
        - mfaEnabled
      example: mfaEnabled
  responses:
    UnexpectedError:
      description: unexpected error
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
          examples:
            '400':
              value:
                status: 400
                title: Bad Request
            '401':
              value:
                status: 401
                title: Unauthorized
            '403':
              value:
                status: 403
                title: Forbidden
            '404':
              value:
                status: 404
                title: Not Found
            '405':
              value:
                status: 405
                title: Method Not Allowed
            '406':
              value:
                status: 406
                title: Not Acceptable
  securitySchemes:
    oauth2:
      type: oauth2
      flows:
        authorizationCode:
          authorizationUrl: https://aec.cintoo.com/oauth/authorize
          tokenUrl: https://aec.cintoo.com/oauth/token
          scopes: {}
          refreshUrl: https://aec.cintoo.com/oauth/token
x-tagGroups:
  - name: General
    tags:
      - errors_list
      - internal_information
      - token_management
      - Permissions
      - Authentication
  - name: Resources
    tags:
      - Resources
  - name: Tenant
    tags:
      - Tenant
  - name: Account
    tags:
      - Account
      - Subscription
      - Role
      - User
      - Group
  - name: Subscription
    tags:
      - Usage Report
  - name: Project
    tags:
      - Project
      - Members
      - Workzone
  - name: Project Data
    tags:
      - File
      - Annotation
      - Saved View
      - Share Link
      - Crop
      - Measurement
      - Tag List
      - Tag
      - Video360
  - name: Jobs
    tags:
      - Progress Monitoring
      - Import Model
      - Export Scene
      - Segmentation
  - name: Integrations
    tags:
      - Integrations
      - Konekt
      - Autodesk
      - Procore
  - name: Tutorials
    tags:
      - tuto_upload_file
      - understand_tag_data
