├── .gitignore ├── .pylintrc ├── LICENSE ├── README.md ├── blobs ├── pic1.jpg ├── pic2.jpg ├── text1.txt ├── text2.1.txt └── text2.txt ├── demo.py ├── lib ├── __init__.py ├── blobserverapi.py ├── errors.py ├── managerapi.py ├── url.py └── workflow.py └── openapi ├── 2020.2.yaml └── 2023.2.yaml /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | parts/ 18 | sdist/ 19 | var/ 20 | wheels/ 21 | pip-wheel-metadata/ 22 | share/python-wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .nox/ 42 | .coverage 43 | .coverage.* 44 | .cache 45 | nosetests.xml 46 | coverage.xml 47 | *.cover 48 | *.py,cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | db.sqlite3-journal 61 | 62 | # Flask stuff: 63 | instance/ 64 | .webassets-cache 65 | 66 | # Scrapy stuff: 67 | .scrapy 68 | 69 | # Sphinx documentation 70 | docs/_build/ 71 | 72 | # PyBuilder 73 | target/ 74 | 75 | # Jupyter Notebook 76 | .ipynb_checkpoints 77 | 78 | # IPython 79 | profile_default/ 80 | ipython_config.py 81 | 82 | # pyenv 83 | .python-version 84 | 85 | # pipenv 86 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 87 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 88 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 89 | # install all needed dependencies. 90 | #Pipfile.lock 91 | 92 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 93 | __pypackages__/ 94 | 95 | # Celery stuff 96 | celerybeat-schedule 97 | celerybeat.pid 98 | 99 | # SageMath parsed files 100 | *.sage.py 101 | 102 | # Environments 103 | .env 104 | .venv 105 | env/ 106 | venv/ 107 | ENV/ 108 | env.bak/ 109 | venv.bak/ 110 | 111 | # Spyder project settings 112 | .spyderproject 113 | .spyproject 114 | 115 | # Rope project settings 116 | .ropeproject 117 | 118 | # mkdocs documentation 119 | /site 120 | 121 | # mypy 122 | .mypy_cache/ 123 | .dmypy.json 124 | dmypy.json 125 | 126 | # Pyre type checker 127 | .pyre/ 128 | 129 | # OSX 130 | .DS_Store 131 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [FORMAT] 2 | indent-string=\t 3 | max-line-length=240 4 | disable= 5 | C0114, # missing-module-docstring 6 | C0115, # missing-class-docstring 7 | C0116, # missing-function-docstring 8 | W0702, # bare-except 9 | C0304, # missing-final-newline 10 | W0231, # super-init-not-called 11 | C0321, # multiple-statements 12 | C0103, # invalid-name 13 | W0703, # broad-except 14 | C0200, # consider-using-enumerate 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 GRAPHISOFT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # About 2 | 3 | This repository hosts BIMcloud API specification, a small library and an example application in Python. Please note this is the first released version, upcoming releases will extend/alter the functionalities based on user workflows, performance and security considerations. 4 | 5 | # Specification 6 | 7 | Please refer to [openapi/2023.2.yaml](https://raw.githubusercontent.com/GRAPHISOFT/bimcloud-api/master/openapi/2023.2.yaml) specification. It's in a standard OpenAPI 3.0 (Swagger) format, that can be viewed by using any compatible viewer (VS Code for example, or paste the Github raw url to [the online Swagger viewer](https://petstore.swagger.io/)). 8 | 9 | # Demo 10 | 11 | Demo application (demo.py) is about a simple workflow that tries to get over all operations required to upload, download and delete a file to an arbitrary path of a BIMcloud server. 12 | 13 | Please refer to [lib/workflow.py](https://github.com/GRAPHISOFT/bimcloud-api/blob/master/lib/workflow.py) source code and its comments for detailed information. 14 | 15 | *Notice: since authentication APIs send passwords in clear text, it is advised to configure BIMcloud to get accessible by using https endpoints from the Internet.* 16 | 17 | ## Installation 18 | 19 | The demo console application requires Python 3.7+ with installed [requests](https://2.python-requests.org/) library to run. 20 | 21 | The easiest way to obtain this is just installing the standard [Miniconda](https://docs.conda.io/en/latest/miniconda.html) or [Anaconda](https://www.anaconda.com/distribution/#download-section) environment. It has every requirement provided by default. 22 | 23 | With [invidual Python 3 environment](https://www.python.org/downloads/), [requests](https://2.python-requests.org/) should get installed by [using pip or easy_install](https://2.python-requests.org/en/v2.9.1/user/install/). 24 | 25 | Eg.: 26 | 27 | ```bash 28 | pip install requests 29 | ``` 30 | 31 | ## Run 32 | 33 | The demo is a basic commandline application. Entering: 34 | 35 | ```bash 36 | python ./demo.py --help 37 | ``` 38 | 39 | provides: 40 | 41 | ``` 42 | usage: demo.py [-h] -m MANAGER -u USER -p PASSWORD -c CLIENTID [-d] 43 | 44 | optional arguments: 45 | -h, --help show this help message and exit 46 | -m MANAGER, --manager MANAGER 47 | Url of BIMcloud Manager. 48 | -c CLIENTID, --clientid CLIENTID 49 | 3rd party client id (arbitrary unique string, your 50 | domain for example). 51 | -d, --debug Debug exceptions. 52 | ``` 53 | 54 | That should be obvious. Enter this for example to get the demo rolling: 55 | 56 | ```bash 57 | python ./demo.py -m= -u= -p= -clientid= 58 | ``` 59 | -------------------------------------------------------------------------------- /blobs/pic1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GRAPHISOFT/bimcloud-api/e0e4835a332ab572b9a9da9ed982be778cfff353/blobs/pic1.jpg -------------------------------------------------------------------------------- /blobs/pic2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GRAPHISOFT/bimcloud-api/e0e4835a332ab572b9a9da9ed982be778cfff353/blobs/pic2.jpg -------------------------------------------------------------------------------- /blobs/text1.txt: -------------------------------------------------------------------------------- 1 | Hello, world! -------------------------------------------------------------------------------- /blobs/text2.1.txt: -------------------------------------------------------------------------------- 1 | Hello, world! 2 | Hello again! -------------------------------------------------------------------------------- /blobs/text2.txt: -------------------------------------------------------------------------------- 1 | Hello, world! 2 | Hello again! -------------------------------------------------------------------------------- /demo.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import sys 3 | import lib 4 | 5 | def start(): 6 | parser = argparse.ArgumentParser() 7 | parser.add_argument('-m', '--manager', required=True, help='Url of BIMcloud Manager.') 8 | parser.add_argument('-c', '--clientid', required=True, help='3rd party client id (arbitrary unique string, your domain for example).') 9 | parser.add_argument('-d', '--debug', required=False, help='Debug exceptions.', action='store_true') 10 | args = parser.parse_args() 11 | 12 | wf = lib.Workflow(args.manager, args.clientid) 13 | try: 14 | wf.run() 15 | except Exception as err: 16 | print(getattr(err, 'message', str(err) or repr(err)), file=sys.stderr) 17 | if args.debug: 18 | raise err 19 | else: 20 | exit(1) 21 | 22 | if __name__ == '__main__': 23 | start() 24 | -------------------------------------------------------------------------------- /lib/__init__.py: -------------------------------------------------------------------------------- 1 | from .workflow import Workflow -------------------------------------------------------------------------------- /lib/blobserverapi.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from .errors import raise_bimcloud_blob_server_error, BIMcloudBlobServerError, HttpError 3 | from .url import is_url, join_url 4 | 5 | class BlobServerApi: 6 | def __init__(self, server_url): 7 | if not is_url(server_url): 8 | raise ValueError('Server url is invalid.') 9 | 10 | self.server_url = server_url 11 | 12 | def create_session(self, username, ticket): 13 | request = { 14 | 'data-content-type': 'application/vnd.graphisoft.teamwork.session-service-1.0.authentication-request-1.0+json', 15 | 'data': { 16 | 'username': username, 17 | 'ticket': ticket 18 | } 19 | } 20 | url = join_url(self.server_url, 'session-service/1.0/create-session') 21 | response = requests.post(url, json=request, headers={ 'content-type': request['data-content-type'] }) 22 | result = self.process_response(response) 23 | return result['data']['id'] 24 | 25 | def close_session(self, session_id): 26 | url = join_url(self.server_url, 'session-service/1.0/close-session') 27 | response = requests.post(url, params={ 'session-id': session_id }) 28 | self.process_response(response) 29 | 30 | def begin_batch_upload(self, session_id, description): 31 | url = join_url(self.server_url, '/blob-store-service/1.0/begin-batch-upload') 32 | response = requests.post(url, 33 | params={ 34 | 'session-id': session_id, 35 | 'description': description 36 | }) 37 | self.process_response(response) 38 | result = self.process_response(response) 39 | return result['data'] 40 | 41 | def commit_batch_upload(self, session_id, batch_id, conflict_behavior='overwrite'): 42 | url = join_url(self.server_url, '/blob-store-service/1.0/commit-batch-upload') 43 | response = requests.post(url, 44 | params={ 45 | 'session-id': session_id, 46 | 'batch-upload-session-id': batch_id, 47 | 'conflict-behavior': conflict_behavior 48 | }) 49 | self.process_response(response) 50 | result = self.process_response(response) 51 | return result['data'] 52 | 53 | def begin_upload(self, session_id, path, namespace_name): 54 | url = join_url(self.server_url, '/blob-store-service/1.0/begin-upload') 55 | response = requests.post(url, 56 | params={ 57 | 'session-id': session_id, 58 | 'blob-name': path, 59 | 'namespace-name': namespace_name 60 | }) 61 | self.process_response(response) 62 | result = self.process_response(response) 63 | return result['data'] 64 | 65 | def commit_upload(self, session_id, upload_id): 66 | url = join_url(self.server_url, '/blob-store-service/1.0/commit-upload') 67 | response = requests.post(url, 68 | params={ 69 | 'session-id': session_id, 70 | 'upload-session-id': upload_id 71 | }) 72 | self.process_response(response) 73 | result = self.process_response(response) 74 | return result['data'] 75 | 76 | def put_blob_content_part(self, session_id, upload_id, data, offset=None): 77 | url = join_url(self.server_url, '/blob-store-service/1.0/put-blob-content-part') 78 | response = requests.post(url, 79 | params={ 80 | 'session-id': session_id, 81 | 'upload-session-id': upload_id, 82 | 'offset': offset if offset else 0, 83 | 'length': len(data) 84 | }, 85 | data=data) 86 | self.process_response(response) 87 | result = self.process_response(response) 88 | return result['data'] 89 | 90 | def get_blob_content(self, session_id, blob_id): 91 | url = join_url(self.server_url, '/blob-store-service/1.0/get-blob-content') 92 | response = requests.get(url, 93 | params={ 94 | 'session-id': session_id, 95 | 'blob-id': blob_id 96 | }, 97 | stream=True) 98 | self.process_response(response, json=False) 99 | return response 100 | 101 | @staticmethod 102 | def process_response(response, json=True): 103 | # ok, status_code, reason, 430: error-code, error-message 104 | has_content = response.content is not None and len(response.content) 105 | try: 106 | if response.ok: 107 | if has_content: 108 | return response.json() if json else response 109 | else: 110 | return None 111 | if response.status_code == 401 or response.status_code == 430: 112 | # 430: BIMcloud Error 113 | assert has_content, 'BIMcloud error should has contet.' 114 | raise_bimcloud_blob_server_error(response.json()) 115 | except BIMcloudBlobServerError as err: 116 | raise err 117 | except: 118 | pass # Model Server gives 200 for invalid paths for legacy reasons 119 | raise HttpError(response) -------------------------------------------------------------------------------- /lib/errors.py: -------------------------------------------------------------------------------- 1 | MANGER_ERRORS = { 2 | 1: 'GenericError', 3 | 2: 'AuthenticationRequiredError', 4 | 3: 'AccessDeniedError', 5 | 4: 'EntityCyclicDependencyError', 6 | 5: 'EntityExistsError', 7 | 6: 'EntityNotFoundError', 8 | 7: 'EntityValidationError', 9 | 8: 'OptimisticLockError', 10 | 9: 'RevisionObsoletedError', 11 | 10: 'LdapConnectionError', 12 | 11: 'LdapInvalidCredentialsError', 13 | 12: 'FileConnectionBaseDnError', 14 | 13: 'ModelServerSideError', 15 | 14: 'ReferenceError', 16 | 15: 'ProhibitDeleteError', 17 | 16: 'LicenseManagerError', 18 | 17: 'ResultLimitExceededError', 19 | 18: 'ModelServerNotCompatibleError', 20 | 19: 'NotEnoughFreeSpaceError', 21 | 20: 'ChangeHostError', 22 | 21: 'GSIDConnectionError', 23 | 22: 'GSIDInvalidCredentialsError', 24 | 23: 'TagAlreadyAssignedError', 25 | 24: 'KeyExistsError', 26 | 25: 'NotAllowedError', 27 | 26: 'NotYetAvailableError', 28 | 27: 'InsufficientLicenseError' 29 | } 30 | 31 | BLOB_SERVER_ERRORS = { 32 | 1: 'GenericError', 33 | 2: 'AuthenticationRequired', 34 | 3: 'AuthenticationFailed', 35 | 4: 'AccessControlTicketExpired', 36 | 5: 'AccessDenied', 37 | 11: 'SessionNotFound', 38 | 12: 'BatchUploadCommitFailed', 39 | 13: 'InvalidBlobContentPart', 40 | 14: 'UploadSessionNotFound', 41 | 15: 'IncompleteUpload', 42 | 16: 'BlobAttachmentNotFound', 43 | 17: 'BlobNamespaceNotFound', 44 | 18: 'BlobRevisionNotFound', 45 | 19: 'BlobChunkNotFound', 46 | 20: 'BlobAlreadyExists', 47 | 21: 'BlobNotFound', 48 | 22: 'BlobAccessDenied', 49 | 23: 'BlobPermissionDenied' 50 | } 51 | 52 | def get_manager_error_id(code): 53 | name = MANGER_ERRORS.get(code) 54 | if name is None: 55 | 'UnknownBIMcloudManagerError' 56 | return name 57 | 58 | def get_blob_server_error_id(code): 59 | name = BLOB_SERVER_ERRORS.get(code) 60 | if name is None: 61 | 'UnknownBIMcloudBlobServerError' 62 | return name 63 | 64 | def raise_bimcloud_manager_error(error_content): 65 | raise BIMcloudManagerError(error_content['error-code'], error_content['error-message']) 66 | 67 | def raise_bimcloud_blob_server_error(error_content): 68 | data = error_content['data'] 69 | raise BIMcloudBlobServerError(data['error-code'], data['error-message']) 70 | 71 | class BIMcloudError(Exception): 72 | def __init__(self, code, message): 73 | id = get_manager_error_id(code) 74 | self.code = code 75 | self.name = id 76 | self.message = message 77 | 78 | class BIMcloudManagerError(BIMcloudError): pass 79 | 80 | class BIMcloudBlobServerError(BIMcloudError): pass 81 | 82 | class HttpError(Exception): 83 | def __init__(self, response): 84 | self.response = response 85 | self.status_code = response.status_code 86 | self.text = response.text 87 | if self.status_code == 200: 88 | self.reason = 'Invalid Model Server route.' 89 | self.message = f'HttpErrror: status={self.status_code}, reason={self.text or self.reason}.' 90 | return 91 | self.reason = response.reason 92 | self.message = f'HttpErrror: status={self.status_code}. {self.text or self.reason}.' -------------------------------------------------------------------------------- /lib/managerapi.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from .errors import raise_bimcloud_manager_error, HttpError 3 | from .url import is_url, join_url, add_params 4 | import webbrowser 5 | 6 | class ManagerApiRequestContext: 7 | def __init__(self, user_id, access_token, refresh_token, access_token_exp, token_type, client_id): 8 | self.user_id = user_id 9 | self._access_token = access_token 10 | self._refresh_token = refresh_token 11 | self.access_token_exp = access_token_exp 12 | self.token_type = token_type 13 | self.client_id = client_id 14 | 15 | class ManagerApi: 16 | def __init__(self, manager_url, safe=True): 17 | if not is_url(manager_url): 18 | raise ValueError('Manager url is invalid.') 19 | 20 | self.manager_url = manager_url 21 | self._api_root = join_url(manager_url, 'management/client') 22 | self._safe = safe 23 | 24 | def open_authorization_page(self, client_id, state): 25 | url = add_params(join_url(self._api_root, 'oauth2', 'authorize'), { 'client_id': client_id, 'state': state }) 26 | webbrowser.open(url, new=0, autoraise=0) 27 | 28 | def get_authorization_code_by_state(self, state): 29 | url = join_url(self._api_root, 'oauth2', 'get-authorization-code-by-state') 30 | response = requests.get(url, params={ 'state': state }, verify=self._safe) 31 | result = self.process_response(response) 32 | return result['status'], result['code'] 33 | 34 | def get_token_by_password_grant(self, username, password, client_id): 35 | request = { 36 | 'grant_type': 'password', 37 | 'username': username, 38 | 'password': password, 39 | 'client_id': client_id 40 | } 41 | url = join_url(self._api_root, 'oauth2', 'token') 42 | response = requests.post(url, data=request, headers={ 'Content-Type': 'application/x-www-form-urlencoded' }, verify=self._safe) 43 | result = self.process_response(response) 44 | return ManagerApiRequestContext(result['user_id'], result['access_token'], result['refresh_token'], result['access_token_exp'], result['token_type'], client_id) 45 | 46 | def get_token_by_refresh_token_grant(self, refresh_token, client_id): 47 | request = { 48 | 'grant_type': 'refresh_token', 49 | 'refresh_token': refresh_token, 50 | 'client_id': client_id 51 | } 52 | url = join_url(self._api_root, 'oauth2', 'token') 53 | response = requests.post(url, data=request, headers={ 'Content-Type': 'application/x-www-form-urlencoded' }, verify=self._safe) 54 | result = self.process_response(response) 55 | return ManagerApiRequestContext(result['user_id'], result['access_token'], result['refresh_token'], result['access_token_exp'], result['token_type'], client_id) 56 | 57 | def get_token_by_authorization_code_grant(self, authorization_code, client_id): 58 | request = { 59 | 'grant_type': 'authorization_code', 60 | 'code': authorization_code, 61 | 'client_id': client_id 62 | } 63 | url = join_url(self._api_root, 'oauth2', 'token') 64 | response = requests.post(url, data=request, headers={ 'Content-Type': 'application/x-www-form-urlencoded' }, verify=self._safe) 65 | result = self.process_response(response) 66 | return ManagerApiRequestContext(result['user_id'], result['access_token'], result['refresh_token'], result['access_token_exp'], result['token_type'], client_id) 67 | 68 | def get_resource(self, auth_context, by_path=None, by_id=None, try_get=False): 69 | if by_id is not None: 70 | return self.get_resource_by_id(auth_context, by_id) 71 | 72 | criterion = None 73 | if by_path is not None: 74 | criterion = { '$eq': { '$path': by_path } } 75 | 76 | try: 77 | return self.get_resource_by_criterion(auth_context, criterion) 78 | except Exception as err: 79 | if try_get: 80 | return None 81 | raise err 82 | 83 | def get_resource_by_id(self, auth_context, resource_id): 84 | if resource_id is None: 85 | raise ValueError('"resource_id"" expected.') 86 | 87 | url = join_url(self._api_root, 'get-resource') 88 | result = self.refresh_on_expiration(requests.get, auth_context, url, params={ 'resource-id': resource_id }, verify=self._safe) 89 | return result 90 | 91 | def get_resources_by_criterion(self, auth_context, criterion, options=None): 92 | if criterion is None: 93 | raise ValueError('"criterion"" expected.') 94 | 95 | url = join_url(self._api_root, 'get-resources-by-criterion') 96 | params = {} 97 | if isinstance(options, dict): 98 | for key in options: 99 | params[key] = options[key] 100 | 101 | result = self.refresh_on_expiration(requests.post, auth_context, url, params=params, json=criterion, verify=self._safe) 102 | assert isinstance(result, list), 'Result is not a list.' 103 | return result 104 | 105 | def get_resource_by_criterion(self, auth_context, criterion, options=None): 106 | result = self.get_resources_by_criterion(auth_context, criterion, options) 107 | return result[0] if result else None 108 | 109 | def create_resource_group(self, auth_context, name, parent_id=None): 110 | url = join_url(self._api_root, 'insert-resource-group') 111 | directory = { 112 | 'name': name, 113 | 'type': 'resourceGroup' 114 | } 115 | result = self.refresh_on_expiration(requests.post, auth_context, url, params={ 'parent-id': parent_id }, json=directory, verify=self._safe) 116 | assert isinstance(result, str), 'Result is not a string.' 117 | return result 118 | 119 | def delete_resource_group(self, auth_context, directory_id): 120 | url = join_url(self._api_root, 'delete-resource-group') 121 | result = self.refresh_on_expiration(requests.delete, auth_context, url, params={ 'resource-id': directory_id }, verify=self._safe) 122 | return result 123 | 124 | def delete_resources_by_id_list(self, auth_context, ids): 125 | url = join_url(self._api_root, 'delete-resources-by-id-list') 126 | result = self.refresh_on_expiration(requests.post, auth_context, url, json={ 'ids': ids }, verify=self._safe) 127 | return result 128 | 129 | def delete_blob(self, auth_context, blob_id): 130 | url = join_url(self._api_root, 'delete-blob') 131 | self.refresh_on_expiration(requests.delete, auth_context, url, params={'resource-id': blob_id }, verify=self._safe) 132 | 133 | def update_blob(self, auth_context, blob): 134 | url = join_url(self._api_root, 'update-blob') 135 | self.refresh_on_expiration(requests.put, auth_context, url, json=blob, verify=self._safe) 136 | 137 | def update_blob_parent(self, auth_context, blob_id, body): 138 | url = join_url(self._api_root, 'update-blob-parent') 139 | self.refresh_on_expiration(requests.post, auth_context, url, params={ 'blob-id': blob_id }, json=body, verify=self._safe) 140 | 141 | def get_blob_changes_for_sync(self, auth_context, path, resource_group_id, from_revision): 142 | url = join_url(self._api_root, 'get-blob-changes-for-sync') 143 | request = { 144 | 'path': path, 145 | 'resourceGroupId': resource_group_id, 146 | 'fromRevision': from_revision 147 | } 148 | result = self.refresh_on_expiration(requests.post, auth_context, url, json=request, verify=self._safe) 149 | assert isinstance(result, object), 'Result is not an object.' 150 | return result 151 | 152 | def get_inherited_default_blob_server_id(self, auth_context, resource_group_id): 153 | url = join_url(self._api_root, 'get-inherited-default-blob-server-id') 154 | result = self.refresh_on_expiration(requests.get, auth_context, url, params={ 'resource-group-id': resource_group_id }, verify=self._safe) 155 | return result 156 | 157 | def get_job(self, auth_context, job_id): 158 | url = join_url(self._api_root, 'get-job') 159 | result = self.refresh_on_expiration(requests.get, auth_context, url, params={ 'job-id': job_id }, verify=self._safe) 160 | return result 161 | 162 | def abort_job(self, auth_context, job_id): 163 | url = join_url(self._api_root, 'get-job') 164 | result = self.refresh_on_expiration(requests.post, auth_context, url, params={ 'job-id': job_id }, verify=self._safe) 165 | return result 166 | 167 | def get_ticket(self, auth_context, resource_id): 168 | url = join_url(self._api_root, 'ticket-generator/get-ticket') 169 | request = { 170 | 'type': 'freeTicket', 171 | 'resources': [resource_id], 172 | 'format': 'base64' 173 | } 174 | result = self.refresh_on_expiration(requests.post, auth_context, url, False, json=request, verify=self._safe) 175 | assert isinstance(result, bytes), 'Result is not a bytes.' 176 | result = result.decode('utf-8') 177 | return result 178 | 179 | def get_user(self, auth_context, user_id): 180 | url = join_url(self._api_root, 'get-user') 181 | result = self.refresh_on_expiration(requests.get, auth_context, url, params={ 'user-id': user_id }, verify=self._safe) 182 | return result 183 | 184 | def refresh_on_expiration(self, req, auth_context, url, responseJson=True, **kwargs): 185 | try: 186 | response = req(url, **kwargs, headers={ 'Authorization': f'Bearer {auth_context._access_token}' }) 187 | return self.process_response(response, json=responseJson) 188 | except HttpError as e: 189 | if e.status_code == 401: 190 | errorJson = e.response.json() 191 | if 'error' in errorJson and errorJson['error'] == 'invalid_token': 192 | result = self.get_token_by_refresh_token_grant(auth_context._refresh_token, auth_context.client_id) 193 | auth_context._access_token = result._access_token 194 | auth_context._refresh_token = result._refresh_token 195 | response = req(url, headers={ 'Authorization': f'Bearer {auth_context._access_token}' }, **kwargs) 196 | return self.process_response(response, json=responseJson) 197 | raise e 198 | 199 | @staticmethod 200 | def process_response(response, json=True): 201 | # ok, status_code, reason, 430: error-code, error-message 202 | has_content = response.content is not None and len(response.content) 203 | if response.ok: 204 | if has_content: 205 | return response.json() if json else response.content 206 | else: 207 | return None 208 | if response.status_code == 430: 209 | # 430: BIMcloud Error 210 | assert has_content, 'BIMcloud error should has contet.' 211 | raise_bimcloud_manager_error(response.json()) 212 | raise HttpError(response) -------------------------------------------------------------------------------- /lib/url.py: -------------------------------------------------------------------------------- 1 | from urllib.parse import urlparse 2 | 3 | def join_url(*parts): 4 | joined = '/'.join(map(lambda part: part.strip('/'), parts)) 5 | if len(parts): 6 | if parts[0].startswith('/'): 7 | joined = '/' + joined 8 | if parts[-1].endswith('/'): 9 | joined += '/' 10 | return joined 11 | 12 | def add_params(url, params): 13 | result = url 14 | if url[-1] == '/': 15 | result = url[:-1] 16 | 17 | first = True 18 | for key, value in params.items(): 19 | if first: 20 | result += f'?{key}={value}' 21 | first = False 22 | else: 23 | result += f'&{key}={value}' 24 | 25 | return result 26 | 27 | def is_url(url): 28 | try: 29 | result = urlparse(url) 30 | return all([result.scheme, result.netloc]) 31 | except ValueError: 32 | return False 33 | 34 | def parse_url(url): 35 | return urlparse(url) -------------------------------------------------------------------------------- /lib/workflow.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import random 3 | import string 4 | import itertools 5 | import os 6 | import requests 7 | import time 8 | import json 9 | from .managerapi import ManagerApi 10 | from .blobserverapi import BlobServerApi 11 | from .url import join_url, parse_url 12 | from .errors import BIMcloudBlobServerError, BIMcloudManagerError 13 | import uuid 14 | 15 | CHARS = list(itertools.chain(string.ascii_lowercase, string.digits)) 16 | PROJECT_ROOT = 'Project Root' 17 | PROJECT_ROOT_ID = 'projectRoot' 18 | 19 | class Workflow: 20 | def __init__(self, manager_url, client_id): 21 | self._manager_api = ManagerApi(manager_url) 22 | 23 | self.client_id = client_id 24 | self.username= None 25 | 26 | self._auth_context = None 27 | 28 | self._root_dir_name = Workflow.to_unique('DEMO_RootDir') 29 | self._sub_dir_name = Workflow.to_unique('DEMO_SubDir') 30 | self._root_dir_data = None 31 | self._sub_dir_data = None 32 | self._inner_dir_path = None 33 | self._model_server_urls = {} 34 | self._blob_server_sessions = {} 35 | 36 | # Changeset polling starts on revision 0 37 | self._next_revision_for_sync = 0 38 | 39 | def run(self): 40 | # WORKFLOW BEGIN 41 | self.login_sso() 42 | try: 43 | self.create_dirs() 44 | self.upload_files() 45 | self.rename_file() 46 | self.move_file() 47 | self.locate_download_and_delete_files() 48 | self.create_directory_tree_and_delete_recursively() 49 | finally: 50 | self.logout() 51 | # WORKFLOW END 52 | 53 | def login_sso(self): 54 | print('Logging in ...') 55 | state = uuid.uuid4() 56 | self._manager_api.open_authorization_page(self.client_id, state) 57 | time.sleep(1) 58 | 59 | authorization_code = None 60 | for i in range(300): 61 | result = self._manager_api.get_authorization_code_by_state(state) 62 | print (result) 63 | if result[0] == 'succeeded': 64 | authorization_code = result[1] 65 | break 66 | elif result[0] == 'pending': 67 | print('Waiting for login ...') 68 | time.sleep(1) 69 | 70 | if authorization_code is None: 71 | print('Login failed') 72 | quit(1) 73 | 74 | print('Exchanging authorization code for access & refresh token') 75 | self._auth_context = self._manager_api.get_token_by_authorization_code_grant(authorization_code, self.client_id) 76 | print(f'Received token type is "{self._auth_context.token_type}"') 77 | print(f'Access token is going to expire at {Workflow.convert_timestamp(self._auth_context.access_token_exp)}') 78 | print('Logged in.') 79 | 80 | self.username = self._manager_api.get_user(self._auth_context, self._auth_context.user_id)['username'] 81 | 82 | def create_dirs(self): 83 | print('Creating directories ...') 84 | self._root_dir_data = self.get_or_create_dir(self._root_dir_name) 85 | self._sub_dir_data = self.get_or_create_dir(self._sub_dir_name, self._root_dir_data) 86 | print('Directories created.') 87 | 88 | def upload_files(self): 89 | print('Uploading files ...') 90 | 91 | self.upload_file(self._root_dir_data['$path'], 'pic1.jpg') 92 | self.upload_file(self._sub_dir_data['$path'], 'text1.txt') 93 | self.upload_file(self._sub_dir_data['$path'], 'text2.txt') 94 | 95 | # Addign a new version of an already existsing file. 96 | # You can find file versions on Manager's UI. 97 | self.upload_file(self._sub_dir_data['$path'], 'text2.1.txt', 'text2.txt') 98 | 99 | # We can even upload files to non-existsing paths, 100 | # required directories will get createad. 101 | self._inner_dir_path = join_url(self._sub_dir_data['$path'], self.to_unique('foo'), self.to_unique('bar')) 102 | self.upload_file(self._inner_dir_path, 'pic2.jpg') 103 | 104 | self.wait_for_blob_changes() 105 | 106 | print('\nFiles uploaded.') 107 | 108 | def upload_file(self, path, name, alias=None): 109 | if not alias: 110 | alias = name 111 | file_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'blobs', name)) 112 | description = f'\nUploading file "{file_path}" to "{path}/{alias}" ...' 113 | print(description) 114 | 115 | data = None 116 | with open(file_path, 'rb') as f: data = f.read() 117 | 118 | # To know to which File Server we should upload the file, 119 | # we should get the setting. 120 | immediate_parent_dir = self.find_immediate_parent_dir(path) 121 | immediate_parent_path = immediate_parent_dir['$path'] 122 | print(f'Immediate existing parent directory: "{immediate_parent_path}".') 123 | 124 | configured_blob_server_id = \ 125 | self._manager_api.get_inherited_default_blob_server_id( 126 | self._auth_context, 127 | immediate_parent_dir['id']) 128 | 129 | # Blob Server is a role of a Model Server, basically they are the same thing: 130 | model_server = self._manager_api.get_resource_by_id(self._auth_context, configured_blob_server_id) 131 | model_server_name = model_server['name'] 132 | print(f'Configured host Blob Server: "{ model_server_name }".') 133 | 134 | def do_upload(blob_server_session_id: str, blob_server_api: BlobServerApi): 135 | print('Uploading data ...') 136 | 137 | # For more efficient uploads, we could use one batch for many upload operations, 138 | # and commit them together. 139 | # But for the sake of simplicity, we open a batch for every upload for now. 140 | batch = blob_server_api.begin_batch_upload(blob_server_session_id, description) 141 | 142 | # We should extract the manager side mandatory "Project Root" prefix: 143 | blob_server_file_path = self.create_blob_server_path(path, alias) 144 | 145 | upload = blob_server_api.begin_upload(blob_server_session_id, blob_server_file_path, batch['namespace-name']) 146 | 147 | # It is advised to upload large content in chunks. 148 | CHUNK_SIZE = 1024 * 40 # NOTE: We use 40Kb for the DEMO but in real life it should be around several megabytes! 149 | offset = 0 150 | while offset < len(data): 151 | chunk = data[offset:offset + CHUNK_SIZE] 152 | blob_server_api.put_blob_content_part(blob_server_session_id, upload['id'], chunk, offset=offset) 153 | offset += CHUNK_SIZE 154 | 155 | blob_server_api.commit_upload(blob_server_session_id, upload['id']) 156 | blob_server_api.commit_batch_upload(blob_server_session_id, batch['id']) 157 | 158 | print(f'File uploaded as "{blob_server_file_path}".') 159 | 160 | self.run_with_blob_server_session(model_server, do_upload) 161 | 162 | def rename_file(self): 163 | print('\nRenaming a file ...') 164 | 165 | text1_blob_path = join_url(self._sub_dir_data['$path'], 'text1.txt') 166 | text1_blob = self._manager_api.get_resource(self._auth_context, text1_blob_path) 167 | 168 | update_body = { 169 | # Update body should contains the identifier. 170 | 'id': text1_blob['id'], 171 | # And to-be-updated properties with their new values. 172 | # Please note properties with names starting with '$' cannot get updated from client side, they are read only. (Like $parentId, $path, etc.) 173 | 'name': 'text1_rename.txt' 174 | } 175 | 176 | self._manager_api.update_blob(self._auth_context, update_body) 177 | 178 | self.wait_for_blob_changes() 179 | 180 | def move_file(self): 181 | print('\nMoving a file (updating parent path) ...') 182 | 183 | text2_blob_path = join_url(self._sub_dir_data['$path'], 'text2.txt') 184 | text2_blob = self._manager_api.get_resource(self._auth_context, text2_blob_path) 185 | 186 | body = { 187 | # aka.: move file to its parent's parent directory. 188 | 'parentPath': self._root_dir_data['$path'] 189 | } 190 | 191 | self._manager_api.update_blob_parent(self._auth_context, text2_blob['id'], body) 192 | 193 | self.wait_for_blob_changes() 194 | 195 | def locate_download_and_delete_files(self): 196 | self.locate_download_and_delete_files_in(self._root_dir_data, True) 197 | 198 | def locate_download_and_delete_files_in(self, directory, get_changes=False): 199 | directory_id = directory['id'] 200 | directory_path = directory['$path'] 201 | 202 | print(f'\nGetting content of directory "{directory_path}".') 203 | 204 | # To look up content of a directory, we get all resources 205 | # which have parents set as the directory. 206 | 207 | criterion = { '$eq': { '$parentId': directory_id } } 208 | 209 | # All query APIs have default result limit of 1000 items, 210 | # so you should get contents of directories by using pagination. 211 | limit = 100 # Keep it reasonably small. 212 | options = { 213 | 'sort-by': 'name', 214 | 'skip': 0, 215 | 'limit': limit 216 | } 217 | all_content = [] 218 | while True: 219 | content = self._manager_api.get_resources_by_criterion(self._auth_context, criterion, options) 220 | all_content.extend(content) 221 | if len(content) < limit: 222 | break 223 | options['skip'] += limit 224 | 225 | if not all_content: 226 | print('Directory has no content.') 227 | return 228 | 229 | print(f'Directory has {len(all_content)} resources.') 230 | 231 | # Type of directory is 'resourceGroup' in BIMcloud. 232 | for subdir in filter(lambda i: i['type'] == 'resourceGroup', all_content): 233 | self.locate_download_and_delete_files_in(subdir) 234 | 235 | # Type of file is 'blob' in BIMcloud. 236 | for blob in filter(lambda i: i['type'] == 'blob', all_content): 237 | self.download_and_delete_file(blob) 238 | 239 | if get_changes: 240 | self.wait_for_blob_changes() 241 | 242 | # We do this at last, because non-empty directories cannot get deleted (easily). 243 | self._manager_api.delete_resource_group(self._auth_context, directory_id) 244 | print(f'\nDirectory "{directory_path}" deleted.') 245 | 246 | def create_directory_tree_and_delete_recursively(self): 247 | print('Creating and deleting a directory subtree.') 248 | 249 | # Creating example directory tree structure: 250 | # example_root 251 | # L example_sub1 252 | # L example_sub1_sub1 253 | # L example_sub1_sub2 254 | # L example_sub2 255 | # L example_sub2_sub1 256 | # L example_sub2_sub2 257 | example_root_dir = self.get_or_create_dir(Workflow.to_unique('example_root')) 258 | example_sub1_dir = self.get_or_create_dir(Workflow.to_unique('example_sub1'), example_root_dir) 259 | example_sub2_dir = self.get_or_create_dir(Workflow.to_unique('example_sub2'), example_root_dir) 260 | self.get_or_create_dir(Workflow.to_unique('example_sub1_sub1'), example_sub1_dir) 261 | self.get_or_create_dir(Workflow.to_unique('example_sub1_sub2'), example_sub1_dir) 262 | self.get_or_create_dir(Workflow.to_unique('example_sub2_sub1'), example_sub2_dir) 263 | self.get_or_create_dir(Workflow.to_unique('example_sub2_sub2'), example_sub2_dir) 264 | 265 | print(f'Example directory subtree created in {example_root_dir["name"]}.') 266 | 267 | # We can delete directorys with their entire content recursively by using delete-resources-by-id-list API. 268 | # The API is asynchronous which means the directory won't get deleted as soon as the API call get finished. 269 | # The result of the API is a job that we can poll to get result of the ongoing delete operation. 270 | 271 | print(f'\nStartig job to delete {example_root_dir["name"]} recusively.') 272 | 273 | job = self._manager_api.delete_resources_by_id_list(self._auth_context, [example_root_dir['id']]) 274 | 275 | print(f'Job has been started. Id: {job["id"]}, type: {job["jobType"]}.') 276 | print('\nWaiting to job get completed.') 277 | while job['status'] != 'completed' and job['status'] != 'failed': 278 | print(f'Job stauts is {job["status"]}, polling ...') 279 | time.sleep(0.1) 280 | job = self._manager_api.get_job(self._auth_context, job['id']) 281 | 282 | if job['status'] == 'completed': 283 | print('Job has been completed successfully.') 284 | print(f'Result code: {job["resultCode"]}') 285 | print('Progress:') 286 | print(json.dumps(job['progress'], sort_keys=False, indent=4)) 287 | else: 288 | assert job['status'] == 'failed' 289 | print(f'Job has been falied. Erro code: {job["resultCode"]}, error message: {job["result"]}.') 290 | 291 | def download_and_delete_file(self, blob): 292 | blob_id = blob['id'] 293 | blob_path = blob['$path'] 294 | 295 | blob_model_server_id = blob['modelServerId'] 296 | blob_model_server = self._manager_api.get_resource_by_id(self._auth_context, blob_model_server_id) 297 | 298 | def download(blob_server_session_id, blob_server_api): 299 | print(f'\nDownloading "{blob_path}".') 300 | stream = blob_server_api.get_blob_content(blob_server_session_id, blob_id) 301 | first_byte = None 302 | last_byte = None 303 | size = 0 304 | for chunk in stream.iter_content(chunk_size=8192): 305 | if chunk: 306 | size += len(chunk) 307 | if not first_byte: 308 | first_byte = chunk[0] 309 | last_byte = chunk[-1] 310 | print(f'Downloaded {size} bytes. First byte: {first_byte}, last byte: {last_byte}.') 311 | 312 | self.run_with_blob_server_session(blob_model_server, download) 313 | 314 | self._manager_api.delete_blob(self._auth_context, blob_id) 315 | print(f'\nBlob "{blob_path}" deleted.') 316 | 317 | def run_with_blob_server_session(self, model_server, fn): 318 | blob_server_session_id, blob_server_api = self._blob_server_sessions.get(model_server['id'], (None, None)) 319 | if blob_server_session_id is None: 320 | # There could be Many Model Server urls configured, 321 | # to be able to accessed from different network locations. 322 | # We should pick that one that we can access. 323 | model_server_url = self.find_working_model_server_url(model_server) 324 | blob_server_api = BlobServerApi(model_server_url) 325 | 326 | # Ticket is an authentication token for Model (Blob) Server. 327 | ticket = self._manager_api.get_ticket(self._auth_context, model_server['id']) 328 | 329 | blob_server_session_id = blob_server_api.create_session(self.username, ticket) 330 | 331 | self._blob_server_sessions[model_server['id']] = (blob_server_session_id, blob_server_api) 332 | 333 | try: 334 | return fn(blob_server_session_id, blob_server_api) 335 | except BIMcloudBlobServerError as err: 336 | if err.code == 4 or err.code == 11: 337 | # Session or ticket expired, drop: 338 | del self._blob_server_sessions[model_server['id']] 339 | # Retry: 340 | return self.run_with_blob_server_session(model_server, fn) 341 | raise err 342 | 343 | def find_working_model_server_url(self, model_server): 344 | # We should cache this, because it's static and takes too long to determine: 345 | result_url = self._model_server_urls.get(model_server['id']) 346 | if result_url is not None: 347 | return result_url 348 | 349 | possible_urls = model_server['connectionUrls'] 350 | assert isinstance(possible_urls, list), '"possible_urls" is not a list.' 351 | parsed_manager_url = parse_url(self._manager_api.manager_url) 352 | manager_hostname = parsed_manager_url.hostname 353 | manager_protocol = parsed_manager_url.scheme + ':' 354 | 355 | # Order is important here, urls on top are most likely accessible. 356 | for url in possible_urls: 357 | url = url.replace('$protocol', manager_protocol) 358 | url = url.replace('$hostname', manager_hostname) 359 | try: 360 | response = requests.get(join_url(url, 'application-server-service/get-runtime-id')) 361 | if response.ok: 362 | self._model_server_urls[model_server['id']] = url 363 | return url 364 | except: 365 | pass 366 | model_server_name = model_server['name'] 367 | raise RuntimeError(f'Model Server "{model_server_name}" is unreachable.') 368 | 369 | def find_immediate_parent_dir(self, path): 370 | # We should find the immediate existing (parent) directory of an arbitrary path. 371 | dir_data = self._manager_api.get_resource(self._auth_context, by_path=path, try_get=True) 372 | if dir_data is None or dir_data['type'] != 'resourceGroup': 373 | idx = path.rindex('/') 374 | return self.find_immediate_parent_dir(path[0:idx]) 375 | return dir_data 376 | 377 | def get_or_create_dir(self, name, parent=None): 378 | path_of_dir = name if parent is None else join_url(parent['$path'], name) 379 | path_of_dir = self.ensure_root(path_of_dir) 380 | 381 | print(f'Getting directory "{path_of_dir}" ...') 382 | dir_data = self._manager_api.get_resource(self._auth_context, by_path=path_of_dir) 383 | 384 | if dir_data is not None: 385 | print('Directory exists.') 386 | return dir_data 387 | 388 | print('Directory doesn\'t exist, creating ...') 389 | 390 | dir_id = self._manager_api.create_resource_group( 391 | self._auth_context, 392 | name, 393 | parent['id'] if parent is not None else PROJECT_ROOT_ID) 394 | 395 | dir_data = self._manager_api.get_resource(self._auth_context, by_id=dir_id) 396 | 397 | dir_path = dir_data['$path'] 398 | assert dir_path == path_of_dir, 'Resource created on a wrong path.' 399 | 400 | print('Directory created.') 401 | 402 | return dir_data 403 | 404 | def logout(self): 405 | # Since access tokens are decentralized, manager API is lack of logout methods 406 | for server_id in self._blob_server_sessions: 407 | session_id, api = self._blob_server_sessions[server_id] 408 | api.close_session(session_id) 409 | self._blob_server_sessions = {} 410 | self._auth_context = None 411 | self._model_server_urls = {} 412 | 413 | def wait_for_blob_changes(self): 414 | # It migth take a couple of seconds until the next changeset appears. 415 | for i in range(10): 416 | if self.get_blob_changes(str(i + 1)): 417 | break 418 | time.sleep(3) 419 | 420 | def get_blob_changes(self, attempt): 421 | # Blob Server side changes are accessible for helping synchronization scenarios. 422 | # We support a simple polling mechanism for that, by utilizing the get-blob-changes-for-sync API. 423 | # Changesets are separated by revisions, and synchronization always start at revision 0. 424 | # Revision 0 is a special case, it gives all content in the given directory in its result's "created" array field. 425 | # After revision 0 the next set of changes are are accessible by using the last knonw changeset's "endRevision" value in the request's "fromRevison" parameter. 426 | curr_revision = self._next_revision_for_sync 427 | try: 428 | path = self._root_dir_data['$path'] 429 | print(f'\nAttempt #{attempt}: Getting changes after revision {curr_revision} from: "{path}".\n') 430 | 431 | blob_changes = self._manager_api.get_blob_changes_for_sync(self._auth_context, path, None, curr_revision) 432 | 433 | print(json.dumps(blob_changes, sort_keys=False, indent=4)) 434 | 435 | self._next_revision_for_sync = blob_changes['endRevision'] 436 | except BIMcloudManagerError as err: 437 | if err.code == 9: 438 | # Error code 9 means Revision Obsoleted Error. 439 | # This happen when the underlying content database has been replaced to another one under the hood, 440 | # for example after restoring backups. 441 | # When this happens, synchronization flow should reset, and should get started from revision 0. 442 | # The first response from revision zero will contain the whole content of the of the directory in the new database in the "created" array field of the API result. 443 | # The client should use this as a basis of a new synchronization cycle, and should reinitialize its content according the content of the "created" array. 444 | self._next_revision_for_sync = 0 445 | return self.get_blob_changes(attempt + ':RESET') 446 | else: 447 | raise 448 | return self._next_revision_for_sync != curr_revision 449 | 450 | @staticmethod 451 | def create_blob_server_path(manager_dir_path, file_name): 452 | return join_url(manager_dir_path[len(PROJECT_ROOT):], file_name) 453 | 454 | @staticmethod 455 | def ensure_root(path): 456 | # resource paths under Project Root starts with "Project Root". 457 | return path if path.startswith(PROJECT_ROOT) else join_url(PROJECT_ROOT, path) 458 | 459 | @staticmethod 460 | def to_unique(name): 461 | return f'{name}_{random.choice(CHARS)}{random.choice(CHARS)}{random.choice(CHARS)}{random.choice(CHARS)}' 462 | 463 | @staticmethod 464 | def convert_timestamp(timestamp): 465 | return datetime.datetime.fromtimestamp(timestamp).strftime("%B %d, %Y %I:%M:%S") 466 | -------------------------------------------------------------------------------- /openapi/2020.2.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | servers: 3 | # Added by API Auto Mocking Plugin 4 | - description: BIMcloud Manager 5 | url: https://bimcloud.graphisoft.com 6 | - description: BIMcoud Blob server 7 | url: https://blobserver.graphisoft.com 8 | info: 9 | description: | 10 | BIMcloud API specifiaction. 11 | See the provided example for detailed information. 12 | version: "1.0.0" 13 | title: BIMcloud API 2020.2 (alpha release) 14 | tags: 15 | - name: PortalServer 16 | description: | 17 | User login, session, ticket handling and resource management APIs 18 | - name: BlobServer 19 | description: | 20 | Blob server login/authentication APIs and file management APIs 21 | paths: 22 | # PORTAL SERVER SESSION APIS 23 | 24 | /management/client/create-session: 25 | post: 26 | tags: 27 | - PortalServer 28 | description: Creates a new session for callee. 29 | requestBody: 30 | content: 31 | application/json: 32 | schema: 33 | $ref: '#/components/schemas/CreateSessionRequest' 34 | responses: 35 | 200: 36 | description: 'Creates a new session' 37 | content: 38 | application/json: 39 | schema: 40 | $ref: '#/components/schemas/CreateSessionResponse' 41 | 430: 42 | description: 'Request errors' 43 | content: 44 | application/json: 45 | schema: 46 | $ref: '#/components/schemas/PortalServerError' 47 | 503: 48 | description: 'Server unavailable, retry the request later.' 49 | 50 | /management/client/ping-session: 51 | post: 52 | tags: 53 | - PortalServer 54 | description: Pings an already opened session to prevent expiration. 55 | parameters: 56 | - in: query 57 | name: session-id 58 | schema: 59 | type: string 60 | description: The ID of the session to keep alive 61 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 62 | responses: 63 | 200: 64 | description: 'Session lifetime extended.' 65 | content: 66 | application/json: 67 | schema: 68 | type: object 69 | properties: 70 | user-id: 71 | type: string 72 | example: d290f1ee-6c54-4b01-90e6-d701748f0852 73 | description: The ID of the user whose session got extended 74 | 430: 75 | description: 'Request errors' 76 | content: 77 | application/json: 78 | schema: 79 | $ref: '#/components/schemas/PortalServerError' 80 | 503: 81 | description: 'Server unavailable, retry the request later.' 82 | 83 | /management/client/close-session: 84 | post: 85 | tags: 86 | - PortalServer 87 | description: Closes the session. 88 | parameters: 89 | - in: query 90 | name: session-id 91 | schema: 92 | type: string 93 | description: The ID of the session to close 94 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 95 | responses: 96 | 200: 97 | description: Session has been successfully closed, if session with the provided id was found 98 | 503: 99 | description: 'Server unavailable, retry the request later.' 100 | 101 | /management/client/ticket-generator/get-ticket: 102 | post: 103 | tags: 104 | - PortalServer 105 | description: | 106 | Requests a ticket. The ticket is required for file operations. 107 | parameters: 108 | - in: query 109 | name: session-id 110 | schema: 111 | type: string 112 | description: session-id returned by /management/client/create-session 113 | requestBody: 114 | content: 115 | application/json: 116 | schema: 117 | $ref: '#/components/schemas/GetTicketRequest' 118 | responses: 119 | 200: 120 | description: | 121 | Successful ticket request. When input parameter `format` is `base64`, the resonse content type will be 122 | `application/json`, else it will be `application/octet-stream`. The preferred format is `base64`. 123 | content: 124 | application/json: 125 | schema: 126 | $ref: '#/components/schemas/GetTicketResponseBase64' 127 | application/octet-stream: 128 | schema: 129 | $ref: '#/components/schemas/GetTicketResponseLengthPrefixedBuffer' 130 | 430: 131 | description: 'Request errors' 132 | content: 133 | application/json: 134 | schema: 135 | $ref: '#/components/schemas/PortalServerError' 136 | 503: 137 | description: 'Server unavailable, retry the request later.' 138 | 139 | # PORTAL SERVER RESOURCE MANAGEMENT APIS 140 | 141 | /management/client/get-resource: 142 | get: 143 | tags: 144 | - PortalServer 145 | description: | 146 | Request a single resource by ID. 147 | parameters: 148 | - in: query 149 | name: session-id 150 | schema: 151 | type: string 152 | description: session-id returned by /management/client/create-session 153 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 154 | - in: query 155 | name: resource-id 156 | schema: 157 | type: string 158 | description: The ID of the requested resource 159 | example: 8b4989f6-8c31-12f9-1ae0-75b1da4142fc 160 | responses: 161 | 200: 162 | description: Requested resource. 163 | content: 164 | application/json: 165 | schema: 166 | $ref: '#/components/schemas/Resource' 167 | 430: 168 | description: 'Request errors' 169 | content: 170 | application/json: 171 | schema: 172 | $ref: '#/components/schemas/PortalServerError' 173 | 503: 174 | description: 'Server unavailable, retry the request later.' 175 | 176 | /management/client/get-resources-by-criterion: 177 | post: 178 | tags: 179 | - PortalServer 180 | description: | 181 | Requests an array of resources that match the supplied criterion object 182 | parameters: 183 | - in: query 184 | name: session-id 185 | schema: 186 | type: string 187 | description: session-id returned by /management/client/create-session 188 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 189 | requestBody: 190 | description: The criterion object 191 | content: 192 | application/json: 193 | schema: 194 | $ref: '#/components/schemas/CriterionObject' 195 | responses: 196 | 200: 197 | description: Successful get-resource request 198 | content: 199 | application/json: 200 | schema: 201 | type: array 202 | items: 203 | $ref: '#/components/schemas/Resource' 204 | 430: 205 | description: 'Request errors' 206 | content: 207 | application/json: 208 | schema: 209 | $ref: '#/components/schemas/PortalServerError' 210 | 503: 211 | description: 'Server unavailable, retry the request later.' 212 | 213 | /management/client/insert-resource-group: 214 | post: 215 | tags: 216 | - PortalServer 217 | description: | 218 | Creates a resrource group (folder) entity in the resource tree based on the supplied parameters 219 | parameters: 220 | - in: query 221 | name: session-id 222 | schema: 223 | type: string 224 | description: session-id returned by /management/client/create-session 225 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 226 | - in: query 227 | name: parent-id 228 | schema: 229 | type: string 230 | description: The ID of the parent resource group entity. The root entity's ID is `projectRoot`. 231 | example: 8b4989f6-8c31-12f9-1ae0-75b1da4142fd 232 | requestBody: 233 | description: Resource group description 234 | content: 235 | application/json: 236 | schema: 237 | type: object 238 | properties: 239 | name: 240 | type: string 241 | example: folder2 242 | description: The name of the resource group to be created 243 | type: 244 | type: string 245 | pattern: resourceGroup 246 | example: resourceGroup 247 | description: The type of the resource to be created. 248 | responses: 249 | 200: 250 | description: Successful insert-resource-group request 251 | content: 252 | application/json: 253 | schema: 254 | type: string 255 | example: 8b4989f6-8c31-12f9-1ae0-75b1da4142fc 256 | description: The ID of the created resource group 257 | 430: 258 | description: 'Request errors' 259 | content: 260 | application/json: 261 | schema: 262 | $ref: '#/components/schemas/PortalServerError' 263 | 503: 264 | description: 'Server unavailable, retry the request later.' 265 | 266 | /management/client/delete-resource-group: 267 | delete: 268 | tags: 269 | - PortalServer 270 | description: | 271 | Deletes an **empty** resrource group (folder) entity from the resource tree by ID. 272 | parameters: 273 | - in: query 274 | name: session-id 275 | schema: 276 | type: string 277 | description: session-id returned by /management/client/create-session 278 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 279 | - in: query 280 | name: resource-id 281 | schema: 282 | type: string 283 | description: The ID of the resource group entity to be deleted. 284 | example: 8b4989f6-8c31-12f9-1ae0-75b1da4142fc 285 | responses: 286 | 200: 287 | description: Successful delete-resource-group request 288 | content: 289 | application/json: 290 | schema: 291 | type: object 292 | example: {} 293 | description: Empty object. 294 | 430: 295 | description: 'Request errors' 296 | content: 297 | application/json: 298 | schema: 299 | $ref: '#/components/schemas/PortalServerError' 300 | 503: 301 | description: 'Server unavailable, retry the request later.' 302 | 303 | /management/client/delete-resources-by-id-list: 304 | post: 305 | tags: 306 | - PortalServer 307 | description: | 308 | Starts a job to delete resources by specified identifiers. The job deletes folders recursively. 309 | parameters: 310 | - in: query 311 | name: session-id 312 | schema: 313 | type: string 314 | description: session-id returned by /management/client/create-session 315 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 316 | responses: 317 | 200: 318 | description: Job of type *destroyResources* representing the on-giong operation to delete resources. 319 | content: 320 | application/json: 321 | schema: 322 | $ref: '#/components/schemas/Job' 323 | 430: 324 | description: 'Request errors' 325 | content: 326 | application/json: 327 | schema: 328 | $ref: '#/components/schemas/PortalServerError' 329 | 503: 330 | description: 'Server unavailable, retry the request later.' 331 | 332 | /management/client/update-blob: 333 | put: 334 | tags: 335 | - PortalServer 336 | description: | 337 | Updates a blob entity identified by id. 338 | parameters: 339 | - in: query 340 | name: session-id 341 | schema: 342 | type: string 343 | description: session-id returned by /management/client/create-session 344 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 345 | requestBody: 346 | description: Blob data 347 | content: 348 | application/json: 349 | schema: 350 | type: object 351 | properties: 352 | id: 353 | type: string 354 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 355 | description: The id of the blob to be updated 356 | name: 357 | type: string 358 | example: folder2 359 | description: The new name of the blob 360 | type: 361 | type: string 362 | pattern: blob 363 | example: blob 364 | description: The type of the resource to be created. 365 | responses: 366 | 200: 367 | description: Successful update-blob request 368 | content: 369 | application/json: 370 | schema: 371 | type: object 372 | example: {} 373 | description: Empty object. 374 | 430: 375 | description: 'Request errors' 376 | content: 377 | application/json: 378 | schema: 379 | $ref: '#/components/schemas/PortalServerError' 380 | 503: 381 | description: 'Server unavailable, retry the request later.' 382 | 383 | /management/client/update-blob-parent: 384 | put: 385 | tags: 386 | - PortalServer 387 | description: | 388 | Updates parent of a Blob. Baiscally moves it to a different directory. 389 | parameters: 390 | - in: query 391 | name: session-id 392 | schema: 393 | type: string 394 | description: session-id returned by /management/client/create-session 395 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 396 | - in: query 397 | name: blob-id 398 | schema: 399 | type: string 400 | format: uuid 401 | example: 4D7C7BB1-7AEF-4CD2-9146-0286561D6F85 402 | description: The ID of the blob to update 403 | requestBody: 404 | description: Request body. Use one of its properties to modify parent by path or by id. 405 | content: 406 | application/json: 407 | schema: 408 | type: object 409 | properties: 410 | parentPath: 411 | type: string 412 | example: Project Root/folder2 413 | nullable: true 414 | description: New parent folder's path. 415 | parentId: 416 | type: string 417 | format: uuid 418 | nullable: true 419 | description: New parent folder's id. 420 | responses: 421 | 200: 422 | description: Successful update. 423 | content: 424 | application/json: 425 | schema: 426 | type: boolean 427 | example: true 428 | description: Parent has been updated or stayed the same. 429 | 430: 430 | description: 'Request errors' 431 | content: 432 | application/json: 433 | schema: 434 | $ref: '#/components/schemas/PortalServerError' 435 | 503: 436 | description: 'Server unavailable, retry the request later.' 437 | 438 | /management/client/get-blob-changes-for-sync: 439 | put: 440 | tags: 441 | - PortalServer 442 | description: | 443 | Gets blob changes of a specified resource group from a given revision number. 444 | 445 | Blob Server side changes are accessible for helping synchronization scenarios. 446 | We support a simple polling mechanism for that, by utilizing the get-blob-changes-for-sync API. 447 | Changesets are separated by revisions, and synchronization always start at revision 0. 448 | 449 | Revision 0 is a special case, it gives all content in the given folder in its result's "created" array field. 450 | After revision 0 the next set of changes are are accessible by using the last knonw changeset's "endRevision" value in the request's "fromRevison" parameter. 451 | parameters: 452 | - in: query 453 | name: session-id 454 | schema: 455 | type: string 456 | description: session-id returned by /management/client/create-session 457 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 458 | requestBody: 459 | description: | 460 | Use either resourceGroupId or path as a parameter but sending both is not necessary. 461 | content: 462 | application/json: 463 | schema: 464 | type: object 465 | properties: 466 | resourceGroupId: 467 | type: string 468 | example: d290f1ee-6c54-4b01-90e6-d701748f0852 469 | description: The ID of the resource group 470 | nullable: true 471 | path: 472 | type: string 473 | example: Project Root/folder 474 | description: The path of the resource group 475 | nullable: true 476 | fromRevision: 477 | type: number 478 | example: 1 479 | description: The revision number to get the changes from 480 | responses: 481 | 200: 482 | description: Successful get-blob-changes-for-sync request 483 | content: 484 | application/json: 485 | schema: 486 | type: object 487 | properties: 488 | endRevision: 489 | type: number 490 | example: 1 491 | description: Maximum of the received revisions 492 | created: 493 | type: array 494 | items: 495 | $ref: '#/components/schemas/GetBlobChangesForSyncResponseCreatedOrUpdatedObject' 496 | updated: 497 | type: array 498 | items: 499 | $ref: '#/components/schemas/GetBlobChangesForSyncResponseCreatedOrUpdatedObject' 500 | deleted: 501 | type: array 502 | items: 503 | type: object 504 | properties: 505 | id: 506 | type: string 507 | format: uuid 508 | path: 509 | type: string 510 | example: Project Root/folder1 511 | 430: 512 | description: | 513 | Request errors. 514 | 515 | Error code 9 is a special case there and it means Revision Obsoleted Error. 516 | This happen when the underlying content database has been replaced to another one under the hood, 517 | for example after restoring backups. 518 | 519 | When this happens, synchronization flow should reset, and should get started from revision 0. 520 | The first request from revision zero will contain the whole content of the folder in the new database in the "created" array field of the API result. 521 | The client should use this as a basis of a new synchronization cycle, and should reinitializa its content according the content of the "created" array. 522 | content: 523 | application/json: 524 | schema: 525 | $ref: '#/components/schemas/PortalServerError' 526 | 503: 527 | description: 'Server unavailable, retry the request later.' 528 | 529 | /management/client/delete-blob: 530 | delete: 531 | tags: 532 | - PortalServer 533 | description: | 534 | Deletes a file entity from the resource tree by ID. 535 | parameters: 536 | - in: query 537 | name: session-id 538 | schema: 539 | type: string 540 | description: session-id returned by /management/client/create-session 541 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 542 | - in: query 543 | name: resource-id 544 | schema: 545 | type: string 546 | description: The ID of the blob entity to be deleted. 547 | example: 33E36B98-2758-4C17-83E5-69E93B8B87CB 548 | responses: 549 | 200: 550 | description: Successful delete-blob request 551 | content: 552 | application/json: 553 | schema: 554 | type: object 555 | example: {} 556 | description: Empty object. 557 | 430: 558 | description: 'Request errors' 559 | content: 560 | application/json: 561 | schema: 562 | $ref: '#/components/schemas/PortalServerError' 563 | 503: 564 | description: 'Server unavailable, retry the request later.' 565 | 566 | /management/client/get-inherited-default-blob-server-id: 567 | get: 568 | tags: 569 | - PortalServer 570 | description: | 571 | Requests the inherited default blob server ID of a specified resourceGroup (folder) entitiy 572 | parameters: 573 | - in: query 574 | name: session-id 575 | schema: 576 | type: string 577 | description: session-id returned by /management/client/create-session 578 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 579 | - in: query 580 | name: resource-group-id 581 | schema: 582 | type: string 583 | description: The ID of the resource group entity. The root entity's ID is "projectRoot". 584 | example: 8b4989f6-8c31-12f9-1ae0-75b1da4142fc 585 | responses: 586 | 200: 587 | description: The ID of the inherited default blob server associated with the supplied resrouceGroup. 588 | content: 589 | application/json: 590 | schema: 591 | type: string 592 | example: d290f1ee-6c54-4b01-90e6-d701748f0853 593 | format: uuid 594 | description: The ID of inherited default host server 595 | 430: 596 | description: 'Request errors' 597 | content: 598 | application/json: 599 | schema: 600 | $ref: '#/components/schemas/PortalServerError' 601 | 503: 602 | description: 'Server unavailable, retry the request later.' 603 | 604 | # PORTAL SERVER JOB API 605 | 606 | /management/client/get-job: 607 | get: 608 | tags: 609 | - PortalServer 610 | description: | 611 | Request a single job by ID. 612 | parameters: 613 | - in: query 614 | name: session-id 615 | schema: 616 | type: string 617 | description: session-id returned by /management/client/create-session 618 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 619 | - in: query 620 | name: job-id 621 | schema: 622 | type: string 623 | description: The ID of the requested job 624 | example: 8b4989f6-8c31-12f9-1ae0-75b1da4142fc 625 | responses: 626 | 200: 627 | description: Result Job. 628 | content: 629 | application/json: 630 | schema: 631 | $ref: '#/components/schemas/Job' 632 | 430: 633 | description: | 634 | Common request errors. 635 | 636 | Only meaningful error code there is 6 (EntityNotFoundError) which means Job has been removed the system entirely. 637 | If a job completed or failed, it will stay in the database for a few minutes, but eventually it will get deleted for good. 638 | content: 639 | application/json: 640 | schema: 641 | $ref: '#/components/schemas/PortalServerError' 642 | 503: 643 | description: 'Server unavailable, retry the request later.' 644 | 645 | /management/client/abort-job: 646 | post: 647 | tags: 648 | - PortalServer 649 | description: | 650 | Request to abort a running job. 651 | parameters: 652 | - in: query 653 | name: session-id 654 | schema: 655 | type: string 656 | description: session-id returned by /management/client/create-session 657 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 658 | - in: query 659 | name: job-id 660 | schema: 661 | type: string 662 | description: The ID of the job 663 | example: 8b4989f6-8c31-12f9-1ae0-75b1da4142fc 664 | responses: 665 | 200: 666 | description: | 667 | Responds with *true* if the job was running and was abortable and has been aborted successfully. 668 | 669 | Responds with *false* if it hasn't. 670 | content: 671 | application/json: 672 | schema: 673 | type: boolean 674 | 430: 675 | description: | 676 | Common request errors. 677 | 678 | Only meaningful error code there is 6 (EntityNotFoundError) which means Job has been removed the system entirely. 679 | If a job completed or failed, it will stay in the database for a few minutes, but eventually it will get deleted for good. 680 | content: 681 | application/json: 682 | schema: 683 | $ref: '#/components/schemas/PortalServerError' 684 | 503: 685 | description: 'Server unavailable, retry the request later.' 686 | 687 | # BLOB SERVER SESSION APIS 688 | 689 | /session-service/1.0/create-session: 690 | post: 691 | tags: 692 | - BlobServer 693 | description: | 694 | Creates a session on the BIMcloud Blob Server. Requires a ticket generated by the BIMcloud Portal server on 695 | path `/management/client/ticket-generator/get-ticket`. 696 | requestBody: 697 | content: 698 | application/vnd.graphisoft.teamwork.session-service-1.0.authentication-request-1.0+json: 699 | schema: 700 | $ref: '#/components/schemas/BlobServerAuthenticationRequest' 701 | responses: 702 | 200: 703 | description: Successful blob server create-session request 704 | content: 705 | application/vnd.graphisoft.teamwork.session-service-1.0.session-1.0+json: 706 | schema: 707 | type: object 708 | properties: 709 | data-content-type: 710 | type: string 711 | example: application/vnd.graphisoft.teamwork.session-service-1.0.session-1.0+json 712 | data: 713 | $ref: '#/components/schemas/BlobServerSessionResponse' 714 | 401: 715 | description: | 716 | The supplied ticket is incorrect. The returned error will be: 717 | - `3` `AuthenticationFailed` 718 | content: 719 | application/vnd.graphisoft.teamwork.session-service-1.0.session-1.0+json: 720 | schema: 721 | type: object 722 | properties: 723 | data-content-type: 724 | type: string 725 | example: application/vnd.graphisoft.teamwork.session-service-1.0.session-1.0+json 726 | data: 727 | $ref: '#/components/schemas/BlobServerSessionResponse' 728 | 430: 729 | description: Unsuccessful blob server create-session request 730 | content: 731 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 732 | schema: 733 | $ref: '#/components/schemas/BlobServerDetailedError' 734 | 735 | /session-service/1.0/get-session: 736 | get: 737 | tags: 738 | - BlobServer 739 | description: | 740 | Requests information about an already existing session by ID. 741 | parameters: 742 | - in: query 743 | name: session-id 744 | schema: 745 | type: string 746 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 747 | description: The ID of the session 748 | responses: 749 | 200: 750 | description: Successful blob server get-session request 751 | content: 752 | application/vnd.graphisoft.teamwork.session-service-1.0.session-1.0+json: 753 | schema: 754 | type: object 755 | properties: 756 | data-content-type: 757 | type: string 758 | example: application/vnd.graphisoft.teamwork.session-service-1.0.session-1.0+json 759 | data: 760 | $ref: '#/components/schemas/BlobServerSessionResponse' 761 | 430: 762 | description: Unsuccessful blob server get-session request 763 | content: 764 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 765 | schema: 766 | $ref: '#/components/schemas/BlobServerDetailedError' 767 | 768 | /session-service/1.0/close-session: 769 | post: 770 | tags: 771 | - BlobServer 772 | description: | 773 | Closes the session on the BIMcloud blob server. 774 | parameters: 775 | - in: query 776 | name: session-id 777 | schema: 778 | type: string 779 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 780 | description: the ID of the session to close 781 | responses: 782 | 200: 783 | description: Successful blob server close-session request 784 | 430: 785 | description: Unsuccessful blob server close-session request 786 | content: 787 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 788 | schema: 789 | $ref: '#/components/schemas/BlobServerDetailedError' 790 | 791 | # BLOB STORE BLOB SERVER UPLOAD APIS 792 | 793 | /blob-store-service/1.0/begin-batch-upload: 794 | post: 795 | tags: 796 | - BlobServer 797 | description: | 798 | Creates a batch upload session. 799 | parameters: 800 | - in: query 801 | name: session-id 802 | schema: 803 | type: string 804 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 805 | description: The ID of the blob server session returned by /session-service/1.0/create-session 806 | 807 | - in: query 808 | name: description 809 | schema: 810 | type: string 811 | example: This is the description of the batch upload session. 812 | description: The description of the batch being uploaded. Any URL encoded text. 813 | responses: 814 | 200: 815 | description: Batch upload session successfully created. 816 | content: 817 | application/vnd.graphisoft.teamwork.blob-store-service-1.0.batch-upload-session-1.0+json: 818 | schema: 819 | type: object 820 | properties: 821 | data-content-type: 822 | type: string 823 | example: application/vnd.graphisoft.teamwork.blob-store-service-1.0.batch-upload-session-1.0+json 824 | data: 825 | $ref: '#/components/schemas/BlobServerBatchUploadSessionResponse' 826 | 430: 827 | description: Unsuccessful blob server begin-batch-upload request 828 | content: 829 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 830 | schema: 831 | $ref: '#/components/schemas/BlobServerDetailedError' 832 | 833 | /blob-store-service/1.0/begin-upload: 834 | post: 835 | tags: 836 | - BlobServer 837 | description: | 838 | Creates an upload session. 839 | parameters: 840 | - in: query 841 | name: session-id 842 | schema: 843 | type: string 844 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 845 | description: The ID of the blob server session returned by /session-service/1.0/create-session 846 | - in: query 847 | name: blob-name 848 | schema: 849 | type: string 850 | format: path 851 | example: /folder/file.png 852 | description: The full path of the file to be uploaded 853 | - in: query 854 | name: namespace-name 855 | schema: 856 | type: string 857 | format: uuid 858 | example: 832DBA54-7E0B-463C-B0EF-DB65DAB25745 859 | description: The namespace-name returned by begin-batch-upload 860 | responses: 861 | 200: 862 | description: Upload session successfully created. 863 | content: 864 | application/vnd.graphisoft.teamwork.blob-store-service-1.0.upload-session-1.0+json: 865 | schema: 866 | type: object 867 | properties: 868 | data-content-type: 869 | type: string 870 | example: application/vnd.graphisoft.teamwork.blob-store-service-1.0.upload-session-1.0+json 871 | data: 872 | $ref: '#/components/schemas/BlobServerUploadSessionResponse' 873 | 430: 874 | description: Unsuccessful blob server begin-upload request 875 | content: 876 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 877 | schema: 878 | $ref: '#/components/schemas/BlobServerDetailedError' 879 | 880 | /blob-store-service/1.0/put-blob-content-part: 881 | post: 882 | tags: 883 | - BlobServer 884 | description: | 885 | Uploads a blob chunk. 886 | parameters: 887 | - in: query 888 | name: session-id 889 | schema: 890 | type: string 891 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 892 | description: The ID of the blob server session returned by /session-service/1.0/create-session 893 | 894 | - in: query 895 | name: upload-session-id 896 | schema: 897 | type: string 898 | format: uuid 899 | example: 6894B3DD-74FE-48AE-BD5E-266861659B13 900 | description: The ID of the upload-session this blob chunk belongs to 901 | 902 | - in: query 903 | name: offset 904 | schema: 905 | type: integer 906 | example: "0" 907 | description: The offset of the chunk being uploaded from the beginning of the blob in bytes 908 | 909 | - in: query 910 | name: length 911 | schema: 912 | type: integer 913 | example: 484173 914 | description: The size of the chunk being uploaded. 915 | requestBody: 916 | description: The actual chunk to be uploaded 917 | content: 918 | multipart/form-data: 919 | schema: 920 | type: string 921 | format: binary 922 | example: 923 | responses: 924 | 200: 925 | description: Chunk successfully uploaded. 926 | content: 927 | application/vnd.graphisoft.teamwork.blob-store-service-1.0.upload-session-1.0+json: 928 | schema: 929 | type: object 930 | properties: 931 | data-content-type: 932 | type: string 933 | example: application/vnd.graphisoft.teamwork.blob-store-service-1.0.upload-session-1.0+json 934 | data: 935 | $ref: '#/components/schemas/BlobServerUploadSessionResponse' 936 | 430: 937 | description: Unsuccessful blob server put-blob-content-part request 938 | content: 939 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 940 | schema: 941 | $ref: '#/components/schemas/BlobServerDetailedError' 942 | 943 | /blob-store-service/1.0/commit-upload: 944 | post: 945 | tags: 946 | - BlobServer 947 | description: | 948 | Commits a single blob upload session. 949 | 950 | **IMPORTANT** The blob-id in the response to this request will be a temporary ID. 951 | 952 | The final ID will get assigned when /blob-store-service/1.0/commit-batch-upload is called by the client! 953 | parameters: 954 | - in: query 955 | name: session-id 956 | schema: 957 | type: string 958 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 959 | description: The ID of the blob server session returned by /session-service/1.0/create-session 960 | - in: query 961 | name: upload-session-id 962 | schema: 963 | type: string 964 | format: uuid 965 | example: 6894B3DD-74FE-48AE-BD5E-266861659B13 966 | description: The ID of the upload-session to be committed 967 | responses: 968 | 200: 969 | description: Upload session successfully committed. 970 | content: 971 | application/vnd.graphisoft.teamwork.blob-store-service-1.0.blob-metadata-1.0+json: 972 | schema: 973 | type: object 974 | properties: 975 | data-content-type: 976 | type: string 977 | example: application/vnd.graphisoft.teamwork.blob-store-service-1.0.blob-metadata-1.0+json 978 | data: 979 | $ref: '#/components/schemas/BlobServerBlobMetadataResponse' 980 | 430: 981 | description: Upload session could not be committed. 982 | content: 983 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 984 | schema: 985 | $ref: '#/components/schemas/BlobServerDetailedError' 986 | 987 | /blob-store-service/1.0/commit-batch-upload: 988 | post: 989 | tags: 990 | - BlobServer 991 | description: | 992 | Commits a batch upload session. 993 | 994 | **IMPORTANT** The final IDs of the blobs uploaded in this session will be available in the response body of this request. 995 | parameters: 996 | - in: query 997 | name: session-id 998 | schema: 999 | type: string 1000 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 1001 | description: The ID of the blob server session returned by /session-service/1.0/create-session 1002 | - in: query 1003 | name: batch-upload-session-id 1004 | schema: 1005 | type: string 1006 | format: uuid 1007 | example: 832DBA54-7E0B-463C-B0EF-DB65DAB25745 1008 | description: The ID of the batch-upload-session to be committed 1009 | - in: query 1010 | name: conflict-behavior 1011 | schema: 1012 | type: string 1013 | pattern: overwrite|fail 1014 | example: overwrite 1015 | description: | 1016 | The desired conflict resolution method. 1017 | When the target blob already exists will either overwrite the existing blob or throw an error. 1018 | 1019 | The allowed values are `overwrite` and `fail`. 1020 | responses: 1021 | 200: 1022 | description: Batch upload session successfully committed. 1023 | content: 1024 | application/vnd.graphisoft.teamwork.blob-store-service-1.0.blob-metadata-1.0-list+json: 1025 | schema: 1026 | type: object 1027 | properties: 1028 | data-content-type: 1029 | type: string 1030 | example: application/vnd.graphisoft.teamwork.blob-store-service-1.0.blob-metadata-1.0-list+json 1031 | data: 1032 | type: array 1033 | items: 1034 | $ref: '#/components/schemas/BlobServerBlobMetadataResponse' 1035 | 1036 | 430: 1037 | description: Batch upload session could not be committed. 1038 | content: 1039 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 1040 | schema: 1041 | $ref: '#/components/schemas/BlobServerDetailedError' 1042 | 1043 | /blob-store-service/1.0/get-blob-content: 1044 | get: 1045 | tags: 1046 | - BlobServer 1047 | description: | 1048 | Downloads a single file from the BIMcloud Blob Server 1049 | parameters: 1050 | - in: query 1051 | name: session-id 1052 | schema: 1053 | type: string 1054 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 1055 | description: The ID of the blob server session returned by /session-service/1.0/create-session 1056 | - in: query 1057 | name: blob-id 1058 | schema: 1059 | type: string 1060 | format: uuid 1061 | example: 4D7C7BB1-7AEF-4CD2-9146-0286561D6F85 1062 | description: The final (Portal Server side) ID of the blob to download 1063 | - in: query 1064 | name: filename 1065 | schema: 1066 | type: string 1067 | example: file.png 1068 | description: The desired filename of the data to be downloaded. 1069 | responses: 1070 | 200: 1071 | description: File data downloading. 1072 | content: 1073 | application/octet-stream: 1074 | schema: 1075 | type: string 1076 | format: binary 1077 | example: 1078 | 430: 1079 | description: File can not be downloaded. 1080 | content: 1081 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 1082 | schema: 1083 | $ref: '#/components/schemas/BlobServerDetailedError' 1084 | 1085 | components: 1086 | schemas: 1087 | 1088 | # ENTITY TYPES 1089 | 1090 | Resource: 1091 | type: object 1092 | properties: 1093 | id: 1094 | type: string 1095 | format: uuid 1096 | example: 33E36B98-2758-4C17-83E5-69E93B8B87CB 1097 | description: The ID of the requested resource 1098 | type: 1099 | type: string 1100 | example: blob 1101 | description: The type of the requested resource 1102 | name: 1103 | type: string 1104 | example: file.png 1105 | pattern: ^[^<>:;,?\"*|\/\\\\]+$ 1106 | description: The name of the resource excluding path. Similar to filename. 1107 | $path: 1108 | type: string 1109 | example: 'Project Root/folder/file.png' 1110 | description: The full path of the resource. The root folder is called "Project Root" and may be translated. 1111 | $loweredPath: 1112 | type: string 1113 | example: 'project root/folder/file.png' 1114 | description: Same as $path, but lowercase. 1115 | $ancestors: 1116 | type: array 1117 | items: 1118 | type: object 1119 | properties: 1120 | id: 1121 | type: string 1122 | description: The ancestor's id 1123 | name: 1124 | type: string 1125 | description: The ancestor's name 1126 | example: [ 1127 | { 1128 | id: projectRoot, 1129 | name: Project Root 1130 | }, 1131 | { 1132 | id: 8631e8b4-911d-43c0-ae15-cbd3d49018eb, 1133 | name: folder 1134 | } 1135 | ] 1136 | description: | 1137 | The array of ancestors of the resource entitiy in the resource tree. The 0th item is the root of the tree, 1138 | while subsequent items represent one level of depth in the resource tree. The last item is the direct ancestor 1139 | of the requested resource. 1140 | $parentId: 1141 | type: string 1142 | format: uuid 1143 | example: 8631e8b4-911d-43c0-ae15-cbd3d49018eb 1144 | description: The ID of the requested resource's immediate ancestor. 1145 | $parentName: 1146 | type: string 1147 | example: folder 1148 | description: The name of the requested resource's immediate ancestor. 1149 | $modelServerName: 1150 | type: string 1151 | example: model1 1152 | description: The name of the host server 1153 | $modelServerPath: 1154 | type: string 1155 | example: Server Root/model1 1156 | description: | 1157 | The path of the host server. Similar to $path. The root folder is called "Server Root", which may be translated. 1158 | Servers may be nested in folders in a tree the same way as regular resources. 1159 | modelServerId: 1160 | type: string 1161 | example: d290f1ee-6c54-4b01-90e6-d701748f0853 1162 | format: uuid 1163 | description: The ID of the host server. 1164 | $modifiedDate: 1165 | type: integer 1166 | example: 1585303324545 1167 | description: The timestamp of the last modification made to the resource 1168 | $size: 1169 | type: integer 1170 | example: 484173 1171 | description: The size of the requested resource in bytes. 1172 | 1173 | # RESPONSES & CO. 1174 | 1175 | CreateSessionRequest: 1176 | type: object 1177 | properties: 1178 | client-id: 1179 | description: The ID of the client requesting the session 1180 | type: string 1181 | example: someclient 1182 | username: 1183 | type: string 1184 | example: some-user 1185 | description: "Username of the user initiating the request. NOTE: this is case sensitive." 1186 | password: 1187 | description: | 1188 | **IMPORTANT** The password is in cleartext, so it is **IMPERATIVE** that the API gets published via https 1189 | type: string 1190 | example: P4$$w0rD 1191 | 1192 | CreateSessionResponse: 1193 | type: object 1194 | properties: 1195 | session-id: 1196 | description: The ID of the created session. This ID will be required in every subsequent query. 1197 | type: string 1198 | format: uuid 1199 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 1200 | user-id: 1201 | description: The ID of the user who created the session 1202 | type: string 1203 | format: uuid 1204 | example: d290f1ee-6c54-4b01-90e6-d701748f0852 1205 | expire-timeout: 1206 | description: The amout of time in milliseconds until the session expires upon inactivity 1207 | type: integer 1208 | example: 1234567 1209 | 1210 | CriterionObject: 1211 | type: object 1212 | description: | 1213 | Describes the criteria of a database query, similar to mongodb criterions or SQL WHERE clauses. 1214 | See the example for details. 1215 | 1216 | Operators: $and, $or, $eq, $ne, $like, $gt, $gte, $lt, $lte, $not 1217 | example: 1218 | { 1219 | $and: [ 1220 | { $eq: { name: 'file.png' } }, 1221 | { $eq: { parentId: '8631e8b4-911d-43c0-ae15-cbd3d49018eb' } } 1222 | ] 1223 | } 1224 | 1225 | GetTicketRequest: 1226 | type: object 1227 | properties: 1228 | user-id: 1229 | description: The ID of the user who requested the session. Returned by /management/client/create-session 1230 | type: string 1231 | format: uuid 1232 | example: d290f1ee-6c54-4b01-90e6-d701748f0852 1233 | format: 1234 | description: | 1235 | Controls the format and the response type of the ticket generation. Format `base64` returns the ticket data 1236 | in a string encoded in base64, while `lengthPrefixedBuffer` returns a binary buffer. 1237 | When unspecified, it will default to `lengthPrefixedBuffer`. The preferred format is `base64`. 1238 | type: string 1239 | pattern: base64|lengthPrefixedBuffer 1240 | example: base64 1241 | type: 1242 | description: The type of the ticket to be requested. Only `freeTicket` is supported. 1243 | type: string 1244 | example: freeTicket 1245 | resources: 1246 | description: | 1247 | A one element array containing the ID of the resource that the ticket is requested for. For file management, 1248 | the array should contain the ID of the desired blob server. This ID may be obtained by calling 1249 | `/management/client/get-inherited-default-blob-server-id`, where the `resource-group-id` parameter is 1250 | the ID of the future target upload's parent directory. 1251 | type: array 1252 | items: 1253 | type: string 1254 | format: uuid 1255 | maxLength: 1 1256 | minLength: 1 1257 | example: ['d290f1ee-6c54-4b01-90e6-d701748f0853'] 1258 | 1259 | GetTicketResponseBase64: 1260 | type: string 1261 | format: base64 1262 | example: VGlja2V0IGRhdGEgcmV0dXJuZWQgZnJvbSAvbWFuYWdlbWVudC9jbGllbnQvdGlja2V0LWdlbmVyYXRvci9nZXQtdGlja2V0Cg== 1263 | description: The ticket data encoded in base64. For future blob server queries, provide this ticket as-is. 1264 | 1265 | GetTicketResponseLengthPrefixedBuffer: 1266 | type: string 1267 | format: binary 1268 | example: 1269 | description: Binary ticket data. 1270 | 1271 | BlobServerAuthenticationRequest: 1272 | type: object 1273 | properties: 1274 | data-content-type: 1275 | type: string 1276 | example: application/vnd.graphisoft.teamwork.session-service-1.0.authentication-request-1.0+json 1277 | data: 1278 | type: object 1279 | properties: 1280 | username: 1281 | type: string 1282 | example: some-user 1283 | description: The username of the user requesting the new session 1284 | ticket: 1285 | type: string 1286 | format: base64 1287 | example: VGlja2V0IGRhdGEgcmV0dXJuZWQgZnJvbSAvbWFuYWdlbWVudC9jbGllbnQvdGlja2V0LWdlbmVyYXRvci9nZXQtdGlja2V0Cg== 1288 | description: The base64 ticket data returned by `/management/client/ticket-generator/get-ticket` 1289 | 1290 | BlobServerSessionResponse: 1291 | type: object 1292 | properties: 1293 | id: 1294 | type: string 1295 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 1296 | format: guid 1297 | description: The ID of the session created by the Blob Server 1298 | last-access-time: 1299 | type: integer 1300 | example: 123456 1301 | description: Timestamp of the last access of the Blob Server APIs by this session in seconds. 1302 | expiration-time: 1303 | type: integer 1304 | example: 123456 1305 | description: Timestamp of the expiration time of this session in seconds 1306 | creation-time: 1307 | type: integer 1308 | example: 123456 1309 | description: Timestamp of when this session expires in seconds 1310 | 1311 | BlobServerBatchUploadSessionResponse: 1312 | type: object 1313 | properties: 1314 | id: 1315 | type: string 1316 | example: 832DBA54-7E0B-463C-B0EF-DB65DAB25745 1317 | format: uuid 1318 | description: The ID of the batch upload session created by the Blob Server 1319 | namespace-name: 1320 | type: string 1321 | example: 832DBA54-7E0B-463C-B0EF-DB65DAB25745 1322 | format: uuid 1323 | description: The ID of the namespace associated with the batch upload session created by the Blob Server 1324 | description: 1325 | type: string 1326 | example: description 1327 | description: The description of the batch being uploaded, same as the description sent in the request. 1328 | last-access-time: 1329 | type: integer 1330 | example: 1585234790629 1331 | description: Timestamp of the last access of the Blob Server APIs by this session in milliseconds. 1332 | expiration-time: 1333 | type: integer 1334 | example: 1585249190629 1335 | description: Timestamp of the expiration time of this session in milliseconds. 1336 | creation-time: 1337 | type: integer 1338 | example: 1585234790629 1339 | description: Timestamp of when this session was created in milliseconds. 1340 | events: 1341 | type: array 1342 | items: 1343 | type: string 1344 | example: [] 1345 | description: Names of the events that occured during the session. 1346 | 1347 | BlobServerUploadSessionResponse: 1348 | type: object 1349 | properties: 1350 | id: 1351 | type: string 1352 | example: 6894B3DD-74FE-48AE-BD5E-266861659B13 1353 | format: uuid 1354 | description: The ID of the upload session created by the Blob Server 1355 | target-namespace-name: 1356 | type: string 1357 | example: 832DBA54-7E0B-463C-B0EF-DB65DAB25745 1358 | format: uuid 1359 | description: The namespace associated with the batch upload session that this upload session belongs to 1360 | target-blob-name: 1361 | type: string 1362 | example: /folder/file.png 1363 | description: The filename of the file being uploaded, including the path, as supplied as a query parameter to /blob-store-service/1.0/begin-upload 1364 | target-blob-id: 1365 | type: string 1366 | example: "" 1367 | description: The ID of the blob being created by this upload-session, when target-blob-id is given. 1368 | last-access-time: 1369 | type: integer 1370 | example: 1585234790629 1371 | description: Timestamp of the last access time of this session in milliseconds. 1372 | expiration-time: 1373 | type: integer 1374 | example: 1585249190629 1375 | description: Timestamp of the expiration time of this session in milliseconds. 1376 | creation-time: 1377 | type: integer 1378 | example: 1585234790629 1379 | description: Timestamp of when this session was created in milliseconds. 1380 | uploaded-parts: 1381 | type: array 1382 | items: 1383 | type: object 1384 | properties: 1385 | offset: 1386 | type: integer 1387 | description: The offset of the chunk that was already uploaded from the beginning of the blob in bytes 1388 | example: 0 1389 | length: 1390 | type: integer 1391 | description: The size of the chunk that was already uploaded in bytes 1392 | example: 484173 1393 | description: The parts of this file that were already uploaded. Empty initially. 1394 | 1395 | BlobServerBlobMetadataResponse: 1396 | type: object 1397 | properties: 1398 | standard-metadata: 1399 | type: object 1400 | properties: 1401 | namespace-name: 1402 | type: string 1403 | example: 832DBA54-7E0B-463C-B0EF-DB65DAB25745 1404 | format: uuid 1405 | description: The namespace associated with the batch upload session that the committed upload session belongs to 1406 | blob-name: 1407 | type: string 1408 | example: /folder/file.png 1409 | description: The full path of the file that was uploaded 1410 | blob-id: 1411 | type: string 1412 | example: 33E36B98-2758-4C17-83E5-69E93B8B87CB 1413 | format: uuid 1414 | description: | 1415 | The ID of the uploaded blob. 1416 | The ID is a temporary, non-final identifier until `/blob-store-service/1.0/commit-batch-upload` is called. 1417 | The ID retured during `/blob-store-service/1.0/commit-upload` **will** change during 1418 | `/blob-store-service/1.0/commit-upload`! 1419 | metadata-revision: 1420 | type: string 1421 | format: number 1422 | example: "1" 1423 | description: The metadata revision number of the file that was uploaded. Edits to the file metadata increase this value. 1424 | content-revision: 1425 | type: string 1426 | format: number 1427 | example: "1" 1428 | description: The content revision number of the file that was uploaded. Edits to the contents of the file increase this value. 1429 | access: 1430 | type: string 1431 | example: opened 1432 | description: Describes access status 1433 | last-modified-by-user-name: 1434 | type: string 1435 | example: some-user 1436 | description: The username of the user who initiated the last edit to this file 1437 | last-modified-by-user-id: 1438 | type: string 1439 | format: uuid 1440 | example: d290f1ee-6c54-4b01-90e6-d701748f0852 1441 | description: The ID of the user who initiated the last edit to this file 1442 | last-modified: 1443 | type: string 1444 | format: number 1445 | example: "1585234790835" 1446 | description: The timestamp of the latest change made to this file in milliseconds 1447 | created-by-user-name: 1448 | type: string 1449 | example: some-user 1450 | description: The username of the user who initially uploaded this file 1451 | created-by-user-id: 1452 | type: string 1453 | format: uuid 1454 | example: d290f1ee-6c54-4b01-90e6-d701748f0852 1455 | description: The ID of the user who initially uploaded this file 1456 | created: 1457 | type: string 1458 | format: number 1459 | example: "1585234790835" 1460 | description: The timestamp of the initial upload of this file in milliseconds 1461 | content-disposition: 1462 | type: string 1463 | example: attachment; filename="/folder/file.png" 1464 | description: Content disposition of the file 1465 | content-language: 1466 | type: string 1467 | example: "" 1468 | description: Language of file contents 1469 | content-type: 1470 | type: string 1471 | example: application/octet-stream 1472 | description: Content type of this file 1473 | content-hash-algorithm: 1474 | type: string 1475 | example: SHA256 1476 | description: Name of the algorithm used for hashing the file content 1477 | content-hash: 1478 | type: string 1479 | example: E_UXOOjE-SDi-g_Tq6F7dQAd1dp-C5aLTIy1ThHFvFQ 1480 | description: The hash generated by the hash function described in content-hash-algorithm 1481 | cache-control: 1482 | type: string 1483 | example: no-cache 1484 | description: Describes cache 1485 | e-tag: 1486 | type: string 1487 | example: E_UXOOjE-SDi-g_Tq6F7dQAd1dp-C5aLTIy1ThHFvFQ 1488 | description: The e-tag associated with the file. 1489 | size: 1490 | type: string 1491 | format: number 1492 | example: "484173" 1493 | user-metadata: 1494 | type: object 1495 | description: Reserved for future use. 1496 | 1497 | GetBlobChangesForSyncResponseCreatedOrUpdatedObject: 1498 | type: object 1499 | properties: 1500 | id: 1501 | type: string 1502 | format: uuid 1503 | path: 1504 | type: string 1505 | example: Project Root/folder1/blob1.jpg 1506 | timestamp: 1507 | type: number 1508 | example: 8124389429384.234 1509 | revision: 1510 | type: integer 1511 | example: 1 1512 | 1513 | Job: 1514 | type: object 1515 | properties: 1516 | id: 1517 | type: string 1518 | format: uuid 1519 | authorId: 1520 | type: string 1521 | format: uuid 1522 | nullable: true 1523 | description: Identifier of the user who started the job. 1524 | isService: 1525 | type: boolean 1526 | description: Started by system (true), or by a user (false). 1527 | startedOn: 1528 | type: number 1529 | description: Start time (JavaScript timestamp). 1530 | progressedOn: 1531 | type: number 1532 | description: Last progress time (JavaScript timestamp). 1533 | status: 1534 | type: string 1535 | enum: ['starting', 'running', 'failed', 'completed', 'aborted', 'aborting', 'undoing', 'abort failed'] 1536 | description: > 1537 | Job status: 1538 | * `starting` - Job is about to get started. 1539 | * `running` - Job is running. 1540 | * `failed` - Job failed. 1541 | * `completed` - Job completed. 1542 | * `aborted` - Job aborted. 1543 | * `aborting` - Job has been aborted, and processing its abort state. Status will go to *aborted* or *abort failed* from there eventually. 1544 | * `undoing` - Job has been failed, and undoing its partially completed operation. Status will go to *failed* from there eventually. 1545 | * `abort failed` - Job aborted, but there was an error while doing its abort operation. 1546 | jobType: 1547 | type: string 1548 | example: destroyResources 1549 | description: Type of the job. 1550 | progress: 1551 | type: object 1552 | properties: 1553 | min: 1554 | type: number 1555 | example: 0 1556 | max: 1557 | type: number 1558 | example: 100 1559 | current: 1560 | type: number 1561 | example: 50 1562 | description: Job actual progress between *min* and *max*. 1563 | phase: 1564 | type: string 1565 | example: removingElements 1566 | description: Phase identifier of multi-phase jobs. Empty string if the job is single phased. 1567 | result: 1568 | type: string 1569 | nullable: true 1570 | description: | 1571 | Job dependent result value if *status* is *completed*. 1572 | 1573 | Error message if *status* is *failed* or *abort failed*. 1574 | resultCode: 1575 | type: string 1576 | nullable: true 1577 | description: | 1578 | Job dependent result code if *status* is *completed*, usually zero. 1579 | 1580 | Error code if *status* is *failed* or *abort failed*. 1581 | abortable: 1582 | type: boolean 1583 | description: | 1584 | If true, job supports abort operation. 1585 | 1586 | 1587 | # FS ERRORS 1588 | 1589 | BlobServerDetailedError: 1590 | type: object 1591 | properties: 1592 | data-content-type: 1593 | type: string 1594 | example: application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json 1595 | data: 1596 | type: object 1597 | properties: 1598 | error-message: 1599 | type: string 1600 | example: "Failed to execute request: 'session-service-create-session'. Error: 'Authentication failed: access control ticket is expired.'." 1601 | description: The error message generated by the Blob Server 1602 | error-code: 1603 | type: integer 1604 | example: 4 1605 | pattern: '[1-5]|1[1-9]|2[0-3]' 1606 | description: | 1607 | Error code-name pairs: 1608 | - `1` `GenericError` 1609 | - `2` `AuthenticationRequired` 1610 | - `3` `AuthenticationFailed` 1611 | - `4` `AccessControlTicketExpired` 1612 | - `5` `AccessDenied` 1613 | - `11` `SessionNotFound` 1614 | - `12` `BatchUploadCommitFailed` 1615 | - `13` `InvalidBlobContentPart` 1616 | - `14` `UploadSessionNotFound` 1617 | - `15` `IncompleteUpload` 1618 | - `16` `BlobAttachmentNotFound` 1619 | - `17` `BlobNamespaceNotFound` 1620 | - `18` `BlobRevisionNotFound` 1621 | - `19` `BlobChunkNotFound` 1622 | - `20` `BlobAlreadyExists` 1623 | - `21` `BlobNotFound` 1624 | - `22` `BlobAccessDenied` 1625 | - `23` `BlobPermissionDenied` 1626 | details: 1627 | type: object 1628 | properties: 1629 | message: 1630 | type: string 1631 | example: "Authentication failed: access control ticket is expired." 1632 | description: The error message generated by the Blob Server 1633 | reason: 1634 | type: string 1635 | example: AccessControlTicketExpired 1636 | description: The internal name of the error thrown. See the description of error-code for details. 1637 | 1638 | # PS ERRORS 1639 | 1640 | PortalServerError: 1641 | type: object 1642 | properties: 1643 | error-code: 1644 | type: integer 1645 | example: 6 1646 | description: | 1647 | The code of the error being thrown. Current error code-name pairs: 1648 | - `1` `GenericError` 1649 | - `2` `AuthenticationRequiredError` 1650 | - `3` `AccessDeniedError` 1651 | - `4` `EntityCyclicDependencyError` 1652 | - `5` `EntityExistsError` 1653 | - `6` `EntityNotFoundError` 1654 | - `7` `EntityValidationError` 1655 | - `8` `OptimisticLockError` 1656 | - `9` `RevisionObsoletedError` 1657 | - `10` `LdapConnectionError` 1658 | - `11` `LdapInvalidCredentialsError` 1659 | - `12` `FileConnectionBaseDnError` 1660 | - `13` `ModelServerSideError` 1661 | - `14` `ReferenceError` 1662 | - `15` `ProhibitDeleteError` 1663 | - `16` `LicenseManagerError` 1664 | - `17` `ResultLimitExceededError` 1665 | - `18` `ModelServerNotCompatibleError` 1666 | - `19` `NotEnoughFreeSpaceError` 1667 | - `20` `ChangeHostError` 1668 | - `21` `GSIDConnectionError` 1669 | - `22` `GSIDInvalidCredentialsError` 1670 | - `23` `TagAlreadyAssignedError` 1671 | - `24` `KeyExistsError` 1672 | - `25` `NotAllowedError` 1673 | - `26` `NotYetAvailableError` 1674 | - `27` `InsufficientLicenseError` 1675 | error-message: 1676 | type: string 1677 | example: 'EntityNotFoundError: No item found by "projectRootz"' 1678 | description: Details about why the error has occured 1679 | -------------------------------------------------------------------------------- /openapi/2023.2.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.0 2 | servers: 3 | # Added by API Auto Mocking Plugin 4 | - description: BIMcloud Manager 5 | url: https://bimcloud.graphisoft.com 6 | - description: BIMcoud Blob server 7 | url: https://blobserver.graphisoft.com 8 | info: 9 | description: | 10 | BIMcloud API specifiaction. 11 | See the provided example for detailed information. 12 | version: "1.0.0" 13 | title: BIMcloud API 2023.2 (alpha release) 14 | tags: 15 | - name: PortalServer 16 | description: | 17 | User login, session, ticket handling and resource management APIs 18 | - name: BlobServer 19 | description: | 20 | Blob server login/authentication APIs and file management APIs 21 | paths: 22 | # PORTAL SERVER SESSION APIS 23 | 24 | /management/client/oauth2/authorize: 25 | get: 26 | tags: 27 | - PortalServer 28 | description: Initiates oauth2 auth flow with an HTML page for user credentials. 29 | parameters: 30 | - in: query 31 | name: state 32 | schema: 33 | type: string 34 | minLength: 16 35 | maxLength: 2048 36 | description: Client specific state identification. Required minimum length 16, maximum length 2048. 37 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 38 | required: true 39 | - in: query 40 | name: redirect_uri 41 | schema: 42 | type: string 43 | description: | 44 | URI of the site to redirect to as a callback. Will include the authorization code as query parameter `code`. 45 | When provided, it has to match with the `redirect_uri` provided in the POST /token request. 46 | When not provided, it defaults to the BIMcloud manager's unique URL 47 | example: https://example.com 48 | required: false 49 | - in: query 50 | name: client_id 51 | schema: 52 | type: string 53 | description: Client specific identification 54 | example: example.com 55 | required: true 56 | - in: query 57 | name: code_challenge_method 58 | schema: 59 | type: string 60 | description: Optional parameter specifying the PKCE code challenge method. Possible values are `plain` or `S256` 61 | example: S256 62 | required: false 63 | - in: query 64 | name: code_challenge 65 | schema: 66 | type: string 67 | minLength: 43 68 | maxLength: 128 69 | description: Optional parameter based on `code_challenge_method`. A string between 43 and 128 characters in length used as code verifier for PKCE. 70 | example: ghltkgalxhuiwycbetrjgbordizrxxuyvbjeglkmhsdm 71 | required: false 72 | - in: query 73 | name: username 74 | schema: 75 | type: string 76 | description: | 77 | Optional parameter for prefilling the username field in the login form. 78 | If provided, the username field will be pre-filled with the provided value. 79 | required: false 80 | 81 | responses: 82 | 200: 83 | description: 'Initiates authentication flow with an HTML page for user credentials.' 84 | 500: 85 | description: 'Code already generated for this state' 86 | content: 87 | application/json: 88 | schema: 89 | $ref: '#/components/schemas/OauthError' 90 | 91 | /management/client/oauth2/logout: 92 | get: 93 | tags: 94 | - PortalServer 95 | description: Serves a HTML page, logs out the user. 96 | parameters: 97 | - in: query 98 | name: state 99 | schema: 100 | type: string 101 | minLength: 16 102 | maxLength: 2048 103 | description: Client specific state identification. Required minimum length 16, maximum length 2048. 104 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 105 | required: true 106 | - in: query 107 | name: redirect_uri 108 | schema: 109 | type: string 110 | description: | 111 | Origin url of the client, the same as the one provided at login. After logging out, a redirect will be initiated to this url. 112 | example: https://example.com 113 | required: true 114 | - in: query 115 | name: client_id 116 | schema: 117 | type: string 118 | description: Client specific identification 119 | example: example.com 120 | required: true 121 | 122 | responses: 123 | 200: 124 | description: 'Initiates authentication flow with an HTML page for user credentials.' 125 | 400: 126 | description: 'Some required parameters are missing.' 127 | content: 128 | application/json: 129 | schema: 130 | $ref: '#/components/schemas/OauthError' 131 | 132 | /management/client/oauth2/get-authorization-code-by-state: 133 | get: 134 | tags: 135 | - PortalServer 136 | description: Polls for the status of authorization code generation 137 | parameters: 138 | - in: query 139 | name: state 140 | schema: 141 | type: string 142 | minLength: 16 143 | maxLength: 2048 144 | description: Client specific state identification. Required minimum length 16, maximum length 2048. 145 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 146 | required: true 147 | 148 | responses: 149 | 200: 150 | description: Authentication code generation status 151 | content: 152 | application/json: 153 | schema: 154 | $ref: '#/components/schemas/GetAuthenticationCodeByStateResponse' 155 | 430: 156 | description: 'Request errors' 157 | content: 158 | application/json: 159 | schema: 160 | $ref: '#/components/schemas/PortalServerError' 161 | 162 | /management/client/oauth2/token: 163 | post: 164 | tags: 165 | - PortalServer 166 | description: Retrieves an access token by `grant_type`. Not every property is required for every `grant_type`. See schema for details. 167 | requestBody: 168 | content: 169 | application/json: 170 | schema: 171 | $ref: '#/components/schemas/GetTokenRequest' 172 | responses: 173 | 200: 174 | description: Successfully retrieved token 175 | content: 176 | application/json: 177 | schema: 178 | $ref: '#/components/schemas/GetTokenResponse' 179 | 400: 180 | description: Invalid request error, invalid client error, unsupported grant type error, unauthorized client error 181 | content: 182 | application/json: 183 | schema: 184 | $ref: '#/components/schemas/OauthError' 185 | 500: 186 | description: Server error 187 | content: 188 | application/json: 189 | schema: 190 | $ref: '#/components/schemas/OauthError' 191 | 192 | /management/client/ticket-generator/get-ticket: 193 | post: 194 | tags: 195 | - PortalServer 196 | description: | 197 | Requests a ticket. The ticket is required for file operations. 198 | parameters: 199 | - in: header 200 | name: Authorization 201 | schema: 202 | type: string 203 | example: Bearer 204 | description: Token type and token 205 | requestBody: 206 | content: 207 | application/json: 208 | schema: 209 | $ref: '#/components/schemas/GetTicketRequest' 210 | responses: 211 | 200: 212 | description: | 213 | Successful ticket request. When input parameter `format` is `base64`, the resonse content type will be 214 | `application/json`, else it will be `application/octet-stream`. The preferred format is `base64`. 215 | content: 216 | application/json: 217 | schema: 218 | $ref: '#/components/schemas/GetTicketResponseBase64' 219 | application/octet-stream: 220 | schema: 221 | $ref: '#/components/schemas/GetTicketResponseLengthPrefixedBuffer' 222 | 401: 223 | description: '`invalid_token` error may be thrown if the provided access token has expired or otherwise invalid' 224 | content: 225 | application/json: 226 | schema: 227 | $ref: '#/components/schemas/OauthError' 228 | 430: 229 | description: 'Request errors' 230 | content: 231 | application/json: 232 | schema: 233 | $ref: '#/components/schemas/PortalServerError' 234 | 503: 235 | description: 'Server unavailable, retry the request later.' 236 | 237 | # PORTAL SERVER RESOURCE MANAGEMENT APIS 238 | 239 | /management/client/get-resource: 240 | get: 241 | tags: 242 | - PortalServer 243 | description: | 244 | Request a single resource by ID. 245 | parameters: 246 | - in: header 247 | name: Authorization 248 | schema: 249 | type: string 250 | example: Bearer 251 | description: Token type and token 252 | - in: query 253 | name: resource-id 254 | schema: 255 | type: string 256 | description: The ID of the requested resource 257 | example: 8b4989f6-8c31-12f9-1ae0-75b1da4142fc 258 | responses: 259 | 200: 260 | description: Requested resource. 261 | content: 262 | application/json: 263 | schema: 264 | $ref: '#/components/schemas/Resource' 265 | 401: 266 | description: '`invalid_token` error may be thrown if the provided access token has expired or otherwise invalid' 267 | content: 268 | application/json: 269 | schema: 270 | $ref: '#/components/schemas/OauthError' 271 | 430: 272 | description: 'Request errors' 273 | content: 274 | application/json: 275 | schema: 276 | $ref: '#/components/schemas/PortalServerError' 277 | 503: 278 | description: 'Server unavailable, retry the request later.' 279 | 280 | /management/client/get-user: 281 | get: 282 | tags: 283 | - PortalServer 284 | description: | 285 | Request a single user by ID. 286 | parameters: 287 | - in: header 288 | name: Authorization 289 | schema: 290 | type: string 291 | example: Bearer 292 | description: Token type and token 293 | - in: query 294 | name: user-id 295 | schema: 296 | type: string 297 | description: The ID of the requested user 298 | example: 211ce018-cce7-44e7-a139-9db45df65142 299 | responses: 300 | 200: 301 | description: Requested user. 302 | content: 303 | application/json: 304 | schema: 305 | $ref: '#/components/schemas/User' 306 | 401: 307 | description: '`invalid_token` error may be thrown if the provided access token has expired or otherwise invalid' 308 | content: 309 | application/json: 310 | schema: 311 | $ref: '#/components/schemas/OauthError' 312 | 430: 313 | description: 'Request errors' 314 | content: 315 | application/json: 316 | schema: 317 | $ref: '#/components/schemas/PortalServerError' 318 | 503: 319 | description: 'Server unavailable, retry the request later.' 320 | 321 | /management/client/get-resources-by-criterion: 322 | post: 323 | tags: 324 | - PortalServer 325 | description: | 326 | Requests an array of resources that match the supplied criterion object 327 | parameters: 328 | - in: header 329 | name: Authorization 330 | schema: 331 | type: string 332 | example: Bearer 333 | description: Token type and token 334 | requestBody: 335 | description: The criterion object 336 | content: 337 | application/json: 338 | schema: 339 | $ref: '#/components/schemas/CriterionObject' 340 | responses: 341 | 200: 342 | description: Successful get-resource request 343 | content: 344 | application/json: 345 | schema: 346 | type: array 347 | items: 348 | $ref: '#/components/schemas/Resource' 349 | 401: 350 | description: '`invalid_token` error may be thrown if the provided access token has expired or otherwise invalid' 351 | content: 352 | application/json: 353 | schema: 354 | $ref: '#/components/schemas/OauthError' 355 | 430: 356 | description: 'Request errors' 357 | content: 358 | application/json: 359 | schema: 360 | $ref: '#/components/schemas/PortalServerError' 361 | 503: 362 | description: 'Server unavailable, retry the request later.' 363 | 364 | /management/client/insert-resource-group: 365 | post: 366 | tags: 367 | - PortalServer 368 | description: | 369 | Creates a resrource group (folder) entity in the resource tree based on the supplied parameters 370 | parameters: 371 | - in: header 372 | name: Authorization 373 | schema: 374 | type: string 375 | example: Bearer 376 | description: Token type and token 377 | - in: query 378 | name: parent-id 379 | schema: 380 | type: string 381 | description: The ID of the parent resource group entity. The root entity's ID is `projectRoot`. 382 | example: 8b4989f6-8c31-12f9-1ae0-75b1da4142fd 383 | requestBody: 384 | description: Resource group description 385 | content: 386 | application/json: 387 | schema: 388 | type: object 389 | properties: 390 | name: 391 | type: string 392 | example: folder2 393 | description: The name of the resource group to be created 394 | type: 395 | type: string 396 | pattern: resourceGroup 397 | example: resourceGroup 398 | description: The type of the resource to be created. 399 | responses: 400 | 200: 401 | description: Successful insert-resource-group request 402 | content: 403 | application/json: 404 | schema: 405 | type: string 406 | example: 8b4989f6-8c31-12f9-1ae0-75b1da4142fc 407 | description: The ID of the created resource group 408 | 401: 409 | description: '`invalid_token` error may be thrown if the provided access token has expired or otherwise invalid' 410 | content: 411 | application/json: 412 | schema: 413 | $ref: '#/components/schemas/OauthError' 414 | 430: 415 | description: 'Request errors' 416 | content: 417 | application/json: 418 | schema: 419 | $ref: '#/components/schemas/PortalServerError' 420 | 503: 421 | description: 'Server unavailable, retry the request later.' 422 | 423 | /management/client/delete-resource-group: 424 | delete: 425 | tags: 426 | - PortalServer 427 | description: | 428 | Deletes an **empty** resrource group (folder) entity from the resource tree by ID. 429 | parameters: 430 | - in: header 431 | name: Authorization 432 | schema: 433 | type: string 434 | example: Bearer 435 | description: Token type and token 436 | - in: query 437 | name: resource-id 438 | schema: 439 | type: string 440 | description: The ID of the resource group entity to be deleted. 441 | example: 8b4989f6-8c31-12f9-1ae0-75b1da4142fc 442 | responses: 443 | 200: 444 | description: Successful delete-resource-group request 445 | content: 446 | application/json: 447 | schema: 448 | type: object 449 | example: {} 450 | description: Empty object. 451 | 401: 452 | description: '`invalid_token` error may be thrown if the provided access token has expired or otherwise invalid' 453 | content: 454 | application/json: 455 | schema: 456 | $ref: '#/components/schemas/OauthError' 457 | 430: 458 | description: 'Request errors' 459 | content: 460 | application/json: 461 | schema: 462 | $ref: '#/components/schemas/PortalServerError' 463 | 503: 464 | description: 'Server unavailable, retry the request later.' 465 | 466 | /management/client/delete-resources-by-id-list: 467 | post: 468 | tags: 469 | - PortalServer 470 | description: | 471 | Starts a job to delete resources by specified identifiers. The job deletes folders recursively. 472 | parameters: 473 | - in: header 474 | name: Authorization 475 | schema: 476 | type: string 477 | example: Bearer 478 | description: Token type and token 479 | responses: 480 | 200: 481 | description: Job of type *destroyResources* representing the on-giong operation to delete resources. 482 | content: 483 | application/json: 484 | schema: 485 | $ref: '#/components/schemas/Job' 486 | 401: 487 | description: '`invalid_token` error may be thrown if the provided access token has expired or otherwise invalid' 488 | content: 489 | application/json: 490 | schema: 491 | $ref: '#/components/schemas/OauthError' 492 | 430: 493 | description: 'Request errors' 494 | content: 495 | application/json: 496 | schema: 497 | $ref: '#/components/schemas/PortalServerError' 498 | 503: 499 | description: 'Server unavailable, retry the request later.' 500 | 501 | /management/client/update-blob: 502 | put: 503 | tags: 504 | - PortalServer 505 | description: | 506 | Updates a blob entity identified by id. 507 | parameters: 508 | - in: header 509 | name: Authorization 510 | schema: 511 | type: string 512 | example: Bearer 513 | description: Token type and token 514 | requestBody: 515 | description: Blob data 516 | content: 517 | application/json: 518 | schema: 519 | type: object 520 | properties: 521 | id: 522 | type: string 523 | example: d290f1ee-6c54-4b01-90e6-d701748f0851 524 | description: The id of the blob to be updated 525 | name: 526 | type: string 527 | example: folder2 528 | description: The new name of the blob 529 | type: 530 | type: string 531 | pattern: blob 532 | example: blob 533 | description: The type of the resource to be created. 534 | responses: 535 | 200: 536 | description: Successful update-blob request 537 | content: 538 | application/json: 539 | schema: 540 | type: object 541 | example: {} 542 | description: Empty object. 543 | 401: 544 | description: '`invalid_token` error may be thrown if the provided access token has expired or otherwise invalid' 545 | content: 546 | application/json: 547 | schema: 548 | $ref: '#/components/schemas/OauthError' 549 | 430: 550 | description: 'Request errors' 551 | content: 552 | application/json: 553 | schema: 554 | $ref: '#/components/schemas/PortalServerError' 555 | 503: 556 | description: 'Server unavailable, retry the request later.' 557 | 558 | /management/client/update-blob-parent: 559 | put: 560 | tags: 561 | - PortalServer 562 | description: | 563 | Updates parent of a Blob. Baiscally moves it to a different directory. 564 | parameters: 565 | - in: header 566 | name: Authorization 567 | schema: 568 | type: string 569 | example: Bearer 570 | description: Token type and token 571 | - in: query 572 | name: blob-id 573 | schema: 574 | type: string 575 | format: uuid 576 | example: 4D7C7BB1-7AEF-4CD2-9146-0286561D6F85 577 | description: The ID of the blob to update 578 | requestBody: 579 | description: Request body. Use one of its properties to modify parent by path or by id. 580 | content: 581 | application/json: 582 | schema: 583 | type: object 584 | properties: 585 | parentPath: 586 | type: string 587 | example: Project Root/folder2 588 | nullable: true 589 | description: New parent folder's path. 590 | parentId: 591 | type: string 592 | format: uuid 593 | nullable: true 594 | description: New parent folder's id. 595 | responses: 596 | 200: 597 | description: Successful update. 598 | content: 599 | application/json: 600 | schema: 601 | type: boolean 602 | example: true 603 | description: Parent has been updated or stayed the same. 604 | 401: 605 | description: '`invalid_token` error may be thrown if the provided access token has expired or otherwise invalid' 606 | content: 607 | application/json: 608 | schema: 609 | $ref: '#/components/schemas/OauthError' 610 | 430: 611 | description: 'Request errors' 612 | content: 613 | application/json: 614 | schema: 615 | $ref: '#/components/schemas/PortalServerError' 616 | 503: 617 | description: 'Server unavailable, retry the request later.' 618 | 619 | /management/client/get-blob-changes-for-sync: 620 | put: 621 | tags: 622 | - PortalServer 623 | description: | 624 | Gets blob changes of a specified resource group from a given revision number. 625 | 626 | Blob Server side changes are accessible for helping synchronization scenarios. 627 | We support a simple polling mechanism for that, by utilizing the get-blob-changes-for-sync API. 628 | Changesets are separated by revisions, and synchronization always start at revision 0. 629 | 630 | Revision 0 is a special case, it gives all content in the given folder in its result's "created" array field. 631 | After revision 0 the next set of changes are are accessible by using the last knonw changeset's "endRevision" value in the request's "fromRevison" parameter. 632 | parameters: 633 | - in: header 634 | name: Authorization 635 | schema: 636 | type: string 637 | example: Bearer 638 | description: Token type and token 639 | requestBody: 640 | description: | 641 | Use either resourceGroupId or path as a parameter but sending both is not necessary. 642 | content: 643 | application/json: 644 | schema: 645 | type: object 646 | properties: 647 | resourceGroupId: 648 | type: string 649 | example: d290f1ee-6c54-4b01-90e6-d701748f0852 650 | description: The ID of the resource group 651 | nullable: true 652 | path: 653 | type: string 654 | example: Project Root/folder 655 | description: The path of the resource group 656 | nullable: true 657 | fromRevision: 658 | type: number 659 | example: 1 660 | description: The revision number to get the changes from 661 | responses: 662 | 200: 663 | description: Successful get-blob-changes-for-sync request 664 | content: 665 | application/json: 666 | schema: 667 | type: object 668 | properties: 669 | endRevision: 670 | type: number 671 | example: 1 672 | description: Maximum of the received revisions 673 | created: 674 | type: array 675 | items: 676 | $ref: '#/components/schemas/GetBlobChangesForSyncResponseCreatedOrUpdatedObject' 677 | updated: 678 | type: array 679 | items: 680 | $ref: '#/components/schemas/GetBlobChangesForSyncResponseCreatedOrUpdatedObject' 681 | deleted: 682 | type: array 683 | items: 684 | type: object 685 | properties: 686 | id: 687 | type: string 688 | format: uuid 689 | path: 690 | type: string 691 | example: Project Root/folder1 692 | 401: 693 | description: '`invalid_token` error may be thrown if the provided access token has expired or otherwise invalid' 694 | content: 695 | application/json: 696 | schema: 697 | $ref: '#/components/schemas/OauthError' 698 | 430: 699 | description: | 700 | Request errors. 701 | 702 | Error code 9 is a special case there and it means Revision Obsoleted Error. 703 | This happen when the underlying content database has been replaced to another one under the hood, 704 | for example after restoring backups. 705 | 706 | When this happens, synchronization flow should reset, and should get started from revision 0. 707 | The first request from revision zero will contain the whole content of the folder in the new database in the "created" array field of the API result. 708 | The client should use this as a basis of a new synchronization cycle, and should reinitializa its content according the content of the "created" array. 709 | content: 710 | application/json: 711 | schema: 712 | $ref: '#/components/schemas/PortalServerError' 713 | 503: 714 | description: 'Server unavailable, retry the request later.' 715 | 716 | /management/client/delete-blob: 717 | delete: 718 | tags: 719 | - PortalServer 720 | description: | 721 | Deletes a file entity from the resource tree by ID. 722 | parameters: 723 | - in: header 724 | name: Authorization 725 | schema: 726 | type: string 727 | example: Bearer 728 | description: Token type and token 729 | - in: query 730 | name: resource-id 731 | schema: 732 | type: string 733 | description: The ID of the blob entity to be deleted. 734 | example: 33E36B98-2758-4C17-83E5-69E93B8B87CB 735 | responses: 736 | 200: 737 | description: Successful delete-blob request 738 | content: 739 | application/json: 740 | schema: 741 | type: object 742 | example: {} 743 | description: Empty object. 744 | 401: 745 | description: '`invalid_token` error may be thrown if the provided access token has expired or otherwise invalid' 746 | content: 747 | application/json: 748 | schema: 749 | $ref: '#/components/schemas/OauthError' 750 | 430: 751 | description: 'Request errors' 752 | content: 753 | application/json: 754 | schema: 755 | $ref: '#/components/schemas/PortalServerError' 756 | 503: 757 | description: 'Server unavailable, retry the request later.' 758 | 759 | /management/client/get-inherited-default-blob-server-id: 760 | get: 761 | tags: 762 | - PortalServer 763 | description: | 764 | Requests the inherited default blob server ID of a specified resourceGroup (folder) entitiy 765 | parameters: 766 | - in: header 767 | name: Authorization 768 | schema: 769 | type: string 770 | example: Bearer 771 | description: Token type and token 772 | - in: query 773 | name: resource-group-id 774 | schema: 775 | type: string 776 | description: The ID of the resource group entity. The root entity's ID is "projectRoot". 777 | example: 8b4989f6-8c31-12f9-1ae0-75b1da4142fc 778 | responses: 779 | 200: 780 | description: The ID of the inherited default blob server associated with the supplied resrouceGroup. 781 | content: 782 | application/json: 783 | schema: 784 | type: string 785 | example: d290f1ee-6c54-4b01-90e6-d701748f0853 786 | format: uuid 787 | description: The ID of inherited default host server 788 | 401: 789 | description: '`invalid_token` error may be thrown if the provided access token has expired or otherwise invalid' 790 | content: 791 | application/json: 792 | schema: 793 | $ref: '#/components/schemas/OauthError' 794 | 430: 795 | description: 'Request errors' 796 | content: 797 | application/json: 798 | schema: 799 | $ref: '#/components/schemas/PortalServerError' 800 | 503: 801 | description: 'Server unavailable, retry the request later.' 802 | 803 | # PORTAL SERVER JOB API 804 | 805 | /management/client/get-job: 806 | get: 807 | tags: 808 | - PortalServer 809 | description: | 810 | Request a single job by ID. 811 | parameters: 812 | - in: header 813 | name: Authorization 814 | schema: 815 | type: string 816 | example: Bearer 817 | description: Token type and token 818 | - in: query 819 | name: job-id 820 | schema: 821 | type: string 822 | description: The ID of the requested job 823 | example: 8b4989f6-8c31-12f9-1ae0-75b1da4142fc 824 | responses: 825 | 200: 826 | description: Result Job. 827 | content: 828 | application/json: 829 | schema: 830 | $ref: '#/components/schemas/Job' 831 | 401: 832 | description: '`invalid_token` error may be thrown if the provided access token has expired or otherwise invalid' 833 | content: 834 | application/json: 835 | schema: 836 | $ref: '#/components/schemas/OauthError' 837 | 430: 838 | description: | 839 | Common request errors. 840 | 841 | Only meaningful error code there is 6 (EntityNotFoundError) which means Job has been removed the system entirely. 842 | If a job completed or failed, it will stay in the database for a few minutes, but eventually it will get deleted for good. 843 | content: 844 | application/json: 845 | schema: 846 | $ref: '#/components/schemas/PortalServerError' 847 | 503: 848 | description: 'Server unavailable, retry the request later.' 849 | 850 | /management/client/abort-job: 851 | post: 852 | tags: 853 | - PortalServer 854 | description: | 855 | Request to abort a running job. 856 | parameters: 857 | - in: header 858 | name: Authorization 859 | schema: 860 | type: string 861 | example: Bearer 862 | description: Token type and token 863 | - in: query 864 | name: job-id 865 | schema: 866 | type: string 867 | description: The ID of the job 868 | example: 8b4989f6-8c31-12f9-1ae0-75b1da4142fc 869 | responses: 870 | 200: 871 | description: | 872 | Responds with *true* if the job was running and was abortable and has been aborted successfully. 873 | 874 | Responds with *false* if it hasn't. 875 | content: 876 | application/json: 877 | schema: 878 | type: boolean 879 | 401: 880 | description: '`invalid_token` error may be thrown if the provided access token has expired or otherwise invalid' 881 | content: 882 | application/json: 883 | schema: 884 | $ref: '#/components/schemas/OauthError' 885 | 430: 886 | description: | 887 | Common request errors. 888 | 889 | Only meaningful error code there is 6 (EntityNotFoundError) which means Job has been removed the system entirely. 890 | If a job completed or failed, it will stay in the database for a few minutes, but eventually it will get deleted for good. 891 | content: 892 | application/json: 893 | schema: 894 | $ref: '#/components/schemas/PortalServerError' 895 | 503: 896 | description: 'Server unavailable, retry the request later.' 897 | 898 | # BLOB SERVER SESSION APIS 899 | 900 | /session-service/1.0/create-session: 901 | post: 902 | tags: 903 | - BlobServer 904 | description: | 905 | Creates a session on the BIMcloud Blob Server. Requires a ticket generated by the BIMcloud Portal server on 906 | path `/management/client/ticket-generator/get-ticket`. 907 | requestBody: 908 | content: 909 | application/vnd.graphisoft.teamwork.session-service-1.0.authentication-request-1.0+json: 910 | schema: 911 | $ref: '#/components/schemas/BlobServerAuthenticationRequest' 912 | responses: 913 | 200: 914 | description: Successful blob server create-session request 915 | content: 916 | application/vnd.graphisoft.teamwork.session-service-1.0.session-1.0+json: 917 | schema: 918 | type: object 919 | properties: 920 | data-content-type: 921 | type: string 922 | example: application/vnd.graphisoft.teamwork.session-service-1.0.session-1.0+json 923 | data: 924 | $ref: '#/components/schemas/BlobServerSessionResponse' 925 | 401: 926 | description: | 927 | The supplied ticket is incorrect. The returned error will be: 928 | - `3` `AuthenticationFailed` 929 | content: 930 | application/vnd.graphisoft.teamwork.session-service-1.0.session-1.0+json: 931 | schema: 932 | type: object 933 | properties: 934 | data-content-type: 935 | type: string 936 | example: application/vnd.graphisoft.teamwork.session-service-1.0.session-1.0+json 937 | data: 938 | $ref: '#/components/schemas/BlobServerSessionResponse' 939 | 430: 940 | description: Unsuccessful blob server create-session request 941 | content: 942 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 943 | schema: 944 | $ref: '#/components/schemas/BlobServerDetailedError' 945 | 946 | /session-service/1.0/get-session: 947 | get: 948 | tags: 949 | - BlobServer 950 | description: | 951 | Requests information about an already existing session by ID. 952 | parameters: 953 | - in: query 954 | name: session-id 955 | schema: 956 | type: string 957 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 958 | description: The ID of the session 959 | responses: 960 | 200: 961 | description: Successful blob server get-session request 962 | content: 963 | application/vnd.graphisoft.teamwork.session-service-1.0.session-1.0+json: 964 | schema: 965 | type: object 966 | properties: 967 | data-content-type: 968 | type: string 969 | example: application/vnd.graphisoft.teamwork.session-service-1.0.session-1.0+json 970 | data: 971 | $ref: '#/components/schemas/BlobServerSessionResponse' 972 | 430: 973 | description: Unsuccessful blob server get-session request 974 | content: 975 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 976 | schema: 977 | $ref: '#/components/schemas/BlobServerDetailedError' 978 | 979 | /session-service/1.0/close-session: 980 | post: 981 | tags: 982 | - BlobServer 983 | description: | 984 | Closes the session on the BIMcloud blob server. 985 | parameters: 986 | - in: query 987 | name: session-id 988 | schema: 989 | type: string 990 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 991 | description: the ID of the session to close 992 | responses: 993 | 200: 994 | description: Successful blob server close-session request 995 | 430: 996 | description: Unsuccessful blob server close-session request 997 | content: 998 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 999 | schema: 1000 | $ref: '#/components/schemas/BlobServerDetailedError' 1001 | 1002 | # BLOB STORE BLOB SERVER UPLOAD APIS 1003 | 1004 | /blob-store-service/1.0/begin-batch-upload: 1005 | post: 1006 | tags: 1007 | - BlobServer 1008 | description: | 1009 | Creates a batch upload session. 1010 | parameters: 1011 | - in: query 1012 | name: session-id 1013 | schema: 1014 | type: string 1015 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 1016 | description: The ID of the blob server session returned by /session-service/1.0/create-session 1017 | 1018 | - in: query 1019 | name: description 1020 | schema: 1021 | type: string 1022 | example: This is the description of the batch upload session. 1023 | description: The description of the batch being uploaded. Any URL encoded text. 1024 | responses: 1025 | 200: 1026 | description: Batch upload session successfully created. 1027 | content: 1028 | application/vnd.graphisoft.teamwork.blob-store-service-1.0.batch-upload-session-1.0+json: 1029 | schema: 1030 | type: object 1031 | properties: 1032 | data-content-type: 1033 | type: string 1034 | example: application/vnd.graphisoft.teamwork.blob-store-service-1.0.batch-upload-session-1.0+json 1035 | data: 1036 | $ref: '#/components/schemas/BlobServerBatchUploadSessionResponse' 1037 | 430: 1038 | description: Unsuccessful blob server begin-batch-upload request 1039 | content: 1040 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 1041 | schema: 1042 | $ref: '#/components/schemas/BlobServerDetailedError' 1043 | 1044 | /blob-store-service/1.0/begin-upload: 1045 | post: 1046 | tags: 1047 | - BlobServer 1048 | description: | 1049 | Creates an upload session. 1050 | parameters: 1051 | - in: query 1052 | name: session-id 1053 | schema: 1054 | type: string 1055 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 1056 | description: The ID of the blob server session returned by /session-service/1.0/create-session 1057 | - in: query 1058 | name: blob-name 1059 | schema: 1060 | type: string 1061 | format: path 1062 | example: /folder/file.png 1063 | description: The full path of the file to be uploaded 1064 | - in: query 1065 | name: namespace-name 1066 | schema: 1067 | type: string 1068 | format: uuid 1069 | example: 832DBA54-7E0B-463C-B0EF-DB65DAB25745 1070 | description: The namespace-name returned by begin-batch-upload 1071 | responses: 1072 | 200: 1073 | description: Upload session successfully created. 1074 | content: 1075 | application/vnd.graphisoft.teamwork.blob-store-service-1.0.upload-session-1.0+json: 1076 | schema: 1077 | type: object 1078 | properties: 1079 | data-content-type: 1080 | type: string 1081 | example: application/vnd.graphisoft.teamwork.blob-store-service-1.0.upload-session-1.0+json 1082 | data: 1083 | $ref: '#/components/schemas/BlobServerUploadSessionResponse' 1084 | 430: 1085 | description: Unsuccessful blob server begin-upload request 1086 | content: 1087 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 1088 | schema: 1089 | $ref: '#/components/schemas/BlobServerDetailedError' 1090 | 1091 | /blob-store-service/1.0/put-blob-content-part: 1092 | post: 1093 | tags: 1094 | - BlobServer 1095 | description: | 1096 | Uploads a blob chunk. 1097 | parameters: 1098 | - in: query 1099 | name: session-id 1100 | schema: 1101 | type: string 1102 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 1103 | description: The ID of the blob server session returned by /session-service/1.0/create-session 1104 | 1105 | - in: query 1106 | name: upload-session-id 1107 | schema: 1108 | type: string 1109 | format: uuid 1110 | example: 6894B3DD-74FE-48AE-BD5E-266861659B13 1111 | description: The ID of the upload-session this blob chunk belongs to 1112 | 1113 | - in: query 1114 | name: offset 1115 | schema: 1116 | type: integer 1117 | example: "0" 1118 | description: The offset of the chunk being uploaded from the beginning of the blob in bytes 1119 | 1120 | - in: query 1121 | name: length 1122 | schema: 1123 | type: integer 1124 | example: 484173 1125 | description: The size of the chunk being uploaded. 1126 | requestBody: 1127 | description: The actual chunk to be uploaded 1128 | content: 1129 | multipart/form-data: 1130 | schema: 1131 | type: string 1132 | format: binary 1133 | example: 1134 | responses: 1135 | 200: 1136 | description: Chunk successfully uploaded. 1137 | content: 1138 | application/vnd.graphisoft.teamwork.blob-store-service-1.0.upload-session-1.0+json: 1139 | schema: 1140 | type: object 1141 | properties: 1142 | data-content-type: 1143 | type: string 1144 | example: application/vnd.graphisoft.teamwork.blob-store-service-1.0.upload-session-1.0+json 1145 | data: 1146 | $ref: '#/components/schemas/BlobServerUploadSessionResponse' 1147 | 430: 1148 | description: Unsuccessful blob server put-blob-content-part request 1149 | content: 1150 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 1151 | schema: 1152 | $ref: '#/components/schemas/BlobServerDetailedError' 1153 | 1154 | /blob-store-service/1.0/commit-upload: 1155 | post: 1156 | tags: 1157 | - BlobServer 1158 | description: | 1159 | Commits a single blob upload session. 1160 | 1161 | **IMPORTANT** The blob-id in the response to this request will be a temporary ID. 1162 | 1163 | The final ID will get assigned when /blob-store-service/1.0/commit-batch-upload is called by the client! 1164 | parameters: 1165 | - in: query 1166 | name: session-id 1167 | schema: 1168 | type: string 1169 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 1170 | description: The ID of the blob server session returned by /session-service/1.0/create-session 1171 | - in: query 1172 | name: upload-session-id 1173 | schema: 1174 | type: string 1175 | format: uuid 1176 | example: 6894B3DD-74FE-48AE-BD5E-266861659B13 1177 | description: The ID of the upload-session to be committed 1178 | responses: 1179 | 200: 1180 | description: Upload session successfully committed. 1181 | content: 1182 | application/vnd.graphisoft.teamwork.blob-store-service-1.0.blob-metadata-1.0+json: 1183 | schema: 1184 | type: object 1185 | properties: 1186 | data-content-type: 1187 | type: string 1188 | example: application/vnd.graphisoft.teamwork.blob-store-service-1.0.blob-metadata-1.0+json 1189 | data: 1190 | $ref: '#/components/schemas/BlobServerBlobMetadataResponse' 1191 | 430: 1192 | description: Upload session could not be committed. 1193 | content: 1194 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 1195 | schema: 1196 | $ref: '#/components/schemas/BlobServerDetailedError' 1197 | 1198 | /blob-store-service/1.0/commit-batch-upload: 1199 | post: 1200 | tags: 1201 | - BlobServer 1202 | description: | 1203 | Commits a batch upload session. 1204 | 1205 | **IMPORTANT** The final IDs of the blobs uploaded in this session will be available in the response body of this request. 1206 | parameters: 1207 | - in: query 1208 | name: session-id 1209 | schema: 1210 | type: string 1211 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 1212 | description: The ID of the blob server session returned by /session-service/1.0/create-session 1213 | - in: query 1214 | name: batch-upload-session-id 1215 | schema: 1216 | type: string 1217 | format: uuid 1218 | example: 832DBA54-7E0B-463C-B0EF-DB65DAB25745 1219 | description: The ID of the batch-upload-session to be committed 1220 | - in: query 1221 | name: conflict-behavior 1222 | schema: 1223 | type: string 1224 | pattern: overwrite|fail 1225 | example: overwrite 1226 | description: | 1227 | The desired conflict resolution method. 1228 | When the target blob already exists will either overwrite the existing blob or throw an error. 1229 | 1230 | The allowed values are `overwrite` and `fail`. 1231 | responses: 1232 | 200: 1233 | description: Batch upload session successfully committed. 1234 | content: 1235 | application/vnd.graphisoft.teamwork.blob-store-service-1.0.blob-metadata-1.0-list+json: 1236 | schema: 1237 | type: object 1238 | properties: 1239 | data-content-type: 1240 | type: string 1241 | example: application/vnd.graphisoft.teamwork.blob-store-service-1.0.blob-metadata-1.0-list+json 1242 | data: 1243 | type: array 1244 | items: 1245 | $ref: '#/components/schemas/BlobServerBlobMetadataResponse' 1246 | 1247 | 430: 1248 | description: Batch upload session could not be committed. 1249 | content: 1250 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 1251 | schema: 1252 | $ref: '#/components/schemas/BlobServerDetailedError' 1253 | 1254 | /blob-store-service/1.0/get-blob-content: 1255 | get: 1256 | tags: 1257 | - BlobServer 1258 | description: | 1259 | Downloads a single file from the BIMcloud Blob Server 1260 | parameters: 1261 | - in: query 1262 | name: session-id 1263 | schema: 1264 | type: string 1265 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 1266 | description: The ID of the blob server session returned by /session-service/1.0/create-session 1267 | - in: query 1268 | name: blob-id 1269 | schema: 1270 | type: string 1271 | format: uuid 1272 | example: 4D7C7BB1-7AEF-4CD2-9146-0286561D6F85 1273 | description: The final (Portal Server side) ID of the blob to download 1274 | - in: query 1275 | name: filename 1276 | schema: 1277 | type: string 1278 | example: file.png 1279 | description: The desired filename of the data to be downloaded. 1280 | responses: 1281 | 200: 1282 | description: File data downloading. 1283 | content: 1284 | application/octet-stream: 1285 | schema: 1286 | type: string 1287 | format: binary 1288 | example: 1289 | 430: 1290 | description: File can not be downloaded. 1291 | content: 1292 | application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json: 1293 | schema: 1294 | $ref: '#/components/schemas/BlobServerDetailedError' 1295 | 1296 | components: 1297 | schemas: 1298 | 1299 | # ENTITY TYPES 1300 | 1301 | Resource: 1302 | type: object 1303 | properties: 1304 | id: 1305 | type: string 1306 | format: uuid 1307 | example: 33E36B98-2758-4C17-83E5-69E93B8B87CB 1308 | description: The ID of the requested resource 1309 | type: 1310 | type: string 1311 | example: blob 1312 | description: The type of the requested resource 1313 | name: 1314 | type: string 1315 | example: file.png 1316 | pattern: ^[^<>:;,?\"*|\/\\\\]+$ 1317 | description: The name of the resource excluding path. Similar to filename. 1318 | $path: 1319 | type: string 1320 | example: 'Project Root/folder/file.png' 1321 | description: The full path of the resource. The root folder is called "Project Root" and may be translated. 1322 | $loweredPath: 1323 | type: string 1324 | example: 'project root/folder/file.png' 1325 | description: Same as $path, but lowercase. 1326 | $ancestors: 1327 | type: array 1328 | items: 1329 | type: object 1330 | properties: 1331 | id: 1332 | type: string 1333 | description: The ancestor's id 1334 | name: 1335 | type: string 1336 | description: The ancestor's name 1337 | example: [ 1338 | { 1339 | id: projectRoot, 1340 | name: Project Root 1341 | }, 1342 | { 1343 | id: 8631e8b4-911d-43c0-ae15-cbd3d49018eb, 1344 | name: folder 1345 | } 1346 | ] 1347 | description: | 1348 | The array of ancestors of the resource entitiy in the resource tree. The 0th item is the root of the tree, 1349 | while subsequent items represent one level of depth in the resource tree. The last item is the direct ancestor 1350 | of the requested resource. 1351 | $parentId: 1352 | type: string 1353 | format: uuid 1354 | example: 8631e8b4-911d-43c0-ae15-cbd3d49018eb 1355 | description: The ID of the requested resource's immediate ancestor. 1356 | $parentName: 1357 | type: string 1358 | example: folder 1359 | description: The name of the requested resource's immediate ancestor. 1360 | $modelServerName: 1361 | type: string 1362 | example: model1 1363 | description: The name of the host server 1364 | $modelServerPath: 1365 | type: string 1366 | example: Server Root/model1 1367 | description: | 1368 | The path of the host server. Similar to $path. The root folder is called "Server Root", which may be translated. 1369 | Servers may be nested in folders in a tree the same way as regular resources. 1370 | modelServerId: 1371 | type: string 1372 | example: d290f1ee-6c54-4b01-90e6-d701748f0853 1373 | format: uuid 1374 | description: The ID of the host server. 1375 | $modifiedDate: 1376 | type: integer 1377 | example: 1585303324545 1378 | description: The timestamp of the last modification made to the resource 1379 | $size: 1380 | type: integer 1381 | example: 484173 1382 | description: The size of the requested resource in bytes. 1383 | 1384 | User: 1385 | type: object 1386 | properties: 1387 | id: 1388 | type: string 1389 | format: uuid 1390 | example: 211ce018-cce7-44e7-a139-9db45df65142 1391 | description: The ID of the requested user 1392 | type: 1393 | type: string 1394 | example: user 1395 | description: The type of the requested user 1396 | name: 1397 | type: string 1398 | example: a 1399 | pattern: ^(^$|[^\\x00-\\x1f\\:*?\"<>|]+)$ 1400 | description: The name of the resource excluding path. Similar to filename. 1401 | username: 1402 | type: string 1403 | example: 'a' 1404 | description: The username of the requested user. 1405 | emailAddress: 1406 | type: string 1407 | example: 'a@a.com' 1408 | description: The email address associated with the requested user. 1409 | active: 1410 | type: boolean 1411 | example: true 1412 | description: Whether the requested user is active. An inactive user may not log in. 1413 | source: 1414 | type: object 1415 | properties: 1416 | directoryServiceId: 1417 | type: string 1418 | example: "" 1419 | format: uuid 1420 | description: | 1421 | The ID of the directory service the requested user was synchronized from. 1422 | Empty if the user is not originated from a directory service. 1423 | id: 1424 | type: string 1425 | example: "" 1426 | description: | 1427 | The ID of the user on the source directory service. 1428 | Empty if the user is not originated from a directory service. 1429 | principal: 1430 | type: string 1431 | example: "" 1432 | description: | 1433 | The principal value of the user on the source directory service. 1434 | Empty if the user is not originated from a directory service. 1435 | directoryServiceType: 1436 | type: string 1437 | example: "" 1438 | description: | 1439 | The type of directory service the user originates from. 1440 | Empty if the user is not originated from a directory service. 1441 | The only possible value is `ldap`. 1442 | passwordLastSet: 1443 | type: number 1444 | example: 0 1445 | description: | 1446 | The timestamp of the last known set password of a user originating from 1447 | a directory service. Local users always stay with 0. The value is synchronized 1448 | from the directory service in regular intervals. During login, this value 1449 | is overwritten when the last known password hash doesn't match. 1450 | properties: 1451 | type: array 1452 | description: | 1453 | An array of objects describing further linked properties from a 1454 | directory service. 1455 | items: 1456 | type: object 1457 | properties: 1458 | name: 1459 | type: string 1460 | example: someProperty 1461 | description: Mapped property name 1462 | value: 1463 | type: string 1464 | example: somePropertyValue 1465 | description: Mapped property value 1466 | linked: 1467 | type: boolean 1468 | example: true 1469 | description: Whether the mapped property is linked to the directory serivce 1470 | color: 1471 | type: object 1472 | description: An object describing the color of the user on the frontend. 1473 | properties: 1474 | r: 1475 | type: number 1476 | example: 0 1477 | description: Red channel 1478 | g: 1479 | type: number 1480 | example: 0 1481 | description: Green channel 1482 | b: 1483 | type: number 1484 | example: 0 1485 | description: Blue channel 1486 | a: 1487 | type: number 1488 | example: 0 1489 | description: Alpha channel 1490 | defaultPermissionSet: 1491 | type: string 1492 | example: "" 1493 | format: uuid 1494 | description: The id of the default permission set of the user. Empty if none. 1495 | enabledNotificationEmails: 1496 | type: boolean 1497 | example: true 1498 | description: Whether the user has the notification emails enabled. 1499 | lastOnline: 1500 | type: number 1501 | example: 0 1502 | description: Legacy. 1503 | enabledMessageNotification: 1504 | type: string 1505 | example: ON 1506 | description: Legacy. 1507 | enabledMessageNotificationEmails: 1508 | type: string 1509 | example: ON 1510 | description: Legacy. 1511 | wantedNotificationLevel: 1512 | type: string 1513 | example: everyNotifications 1514 | description: | 1515 | The level of notifications the user wants to get. 1516 | Possible values: `everyNotifications`, `onlyWarningNotifications`, `noNotifications` 1517 | 1518 | # RESPONSES & CO. 1519 | 1520 | CriterionObject: 1521 | type: object 1522 | description: | 1523 | Describes the criteria of a database query, similar to mongodb criterions or SQL WHERE clauses. 1524 | See the example for details. 1525 | 1526 | Operators: $and, $or, $eq, $ne, $like, $gt, $gte, $lt, $lte, $not 1527 | example: 1528 | { 1529 | $and: [ 1530 | { $eq: { name: 'file.png' } }, 1531 | { $eq: { parentId: '8631e8b4-911d-43c0-ae15-cbd3d49018eb' } } 1532 | ] 1533 | } 1534 | 1535 | GetTicketRequest: 1536 | type: object 1537 | properties: 1538 | user-id: 1539 | description: The ID of the user who requested the session. Included in the response from `/token` 1540 | type: string 1541 | format: uuid 1542 | example: d290f1ee-6c54-4b01-90e6-d701748f0852 1543 | format: 1544 | description: | 1545 | Controls the format and the response type of the ticket generation. Format `base64` returns the ticket data 1546 | in a string encoded in base64, while `lengthPrefixedBuffer` returns a binary buffer. 1547 | When unspecified, it will default to `lengthPrefixedBuffer`. The preferred format is `base64`. 1548 | type: string 1549 | pattern: base64|lengthPrefixedBuffer 1550 | example: base64 1551 | type: 1552 | description: The type of the ticket to be requested. Only `freeTicket` is supported. 1553 | type: string 1554 | example: freeTicket 1555 | resources: 1556 | description: | 1557 | A one element array containing the ID of the resource that the ticket is requested for. For file management, 1558 | the array should contain the ID of the desired blob server. This ID may be obtained by calling 1559 | `/management/client/get-inherited-default-blob-server-id`, where the `resource-group-id` parameter is 1560 | the ID of the future target upload's parent directory. 1561 | type: array 1562 | items: 1563 | type: string 1564 | format: uuid 1565 | maxLength: 1 1566 | minLength: 1 1567 | example: ['d290f1ee-6c54-4b01-90e6-d701748f0853'] 1568 | 1569 | GetTicketResponseBase64: 1570 | type: string 1571 | format: base64 1572 | example: VGlja2V0IGRhdGEgcmV0dXJuZWQgZnJvbSAvbWFuYWdlbWVudC9jbGllbnQvdGlja2V0LWdlbmVyYXRvci9nZXQtdGlja2V0Cg== 1573 | description: The ticket data encoded in base64. For future blob server queries, provide this ticket as-is. 1574 | 1575 | GetTicketResponseLengthPrefixedBuffer: 1576 | type: string 1577 | format: binary 1578 | example: 1579 | description: Binary ticket data. 1580 | 1581 | BlobServerAuthenticationRequest: 1582 | type: object 1583 | properties: 1584 | data-content-type: 1585 | type: string 1586 | example: application/vnd.graphisoft.teamwork.session-service-1.0.authentication-request-1.0+json 1587 | data: 1588 | type: object 1589 | properties: 1590 | username: 1591 | type: string 1592 | example: some-user 1593 | description: The username of the user requesting the new session 1594 | ticket: 1595 | type: string 1596 | format: base64 1597 | example: VGlja2V0IGRhdGEgcmV0dXJuZWQgZnJvbSAvbWFuYWdlbWVudC9jbGllbnQvdGlja2V0LWdlbmVyYXRvci9nZXQtdGlja2V0Cg== 1598 | description: The base64 ticket data returned by `/management/client/ticket-generator/get-ticket` 1599 | 1600 | BlobServerSessionResponse: 1601 | type: object 1602 | properties: 1603 | id: 1604 | type: string 1605 | example: 5cf6ee792cdf05e1ba2b6325c41a5f10 1606 | format: guid 1607 | description: The ID of the session created by the Blob Server 1608 | last-access-time: 1609 | type: integer 1610 | example: 123456 1611 | description: Timestamp of the last access of the Blob Server APIs by this session in seconds. 1612 | expiration-time: 1613 | type: integer 1614 | example: 123456 1615 | description: Timestamp of the expiration time of this session in seconds 1616 | creation-time: 1617 | type: integer 1618 | example: 123456 1619 | description: Timestamp of when this session expires in seconds 1620 | 1621 | BlobServerBatchUploadSessionResponse: 1622 | type: object 1623 | properties: 1624 | id: 1625 | type: string 1626 | example: 832DBA54-7E0B-463C-B0EF-DB65DAB25745 1627 | format: uuid 1628 | description: The ID of the batch upload session created by the Blob Server 1629 | namespace-name: 1630 | type: string 1631 | example: 832DBA54-7E0B-463C-B0EF-DB65DAB25745 1632 | format: uuid 1633 | description: The ID of the namespace associated with the batch upload session created by the Blob Server 1634 | description: 1635 | type: string 1636 | example: description 1637 | description: The description of the batch being uploaded, same as the description sent in the request. 1638 | last-access-time: 1639 | type: integer 1640 | example: 1585234790629 1641 | description: Timestamp of the last access of the Blob Server APIs by this session in milliseconds. 1642 | expiration-time: 1643 | type: integer 1644 | example: 1585249190629 1645 | description: Timestamp of the expiration time of this session in milliseconds. 1646 | creation-time: 1647 | type: integer 1648 | example: 1585234790629 1649 | description: Timestamp of when this session was created in milliseconds. 1650 | events: 1651 | type: array 1652 | items: 1653 | type: string 1654 | example: [] 1655 | description: Names of the events that occured during the session. 1656 | 1657 | BlobServerUploadSessionResponse: 1658 | type: object 1659 | properties: 1660 | id: 1661 | type: string 1662 | example: 6894B3DD-74FE-48AE-BD5E-266861659B13 1663 | format: uuid 1664 | description: The ID of the upload session created by the Blob Server 1665 | target-namespace-name: 1666 | type: string 1667 | example: 832DBA54-7E0B-463C-B0EF-DB65DAB25745 1668 | format: uuid 1669 | description: The namespace associated with the batch upload session that this upload session belongs to 1670 | target-blob-name: 1671 | type: string 1672 | example: /folder/file.png 1673 | description: The filename of the file being uploaded, including the path, as supplied as a query parameter to /blob-store-service/1.0/begin-upload 1674 | target-blob-id: 1675 | type: string 1676 | example: "" 1677 | description: The ID of the blob being created by this upload-session, when target-blob-id is given. 1678 | last-access-time: 1679 | type: integer 1680 | example: 1585234790629 1681 | description: Timestamp of the last access time of this session in milliseconds. 1682 | expiration-time: 1683 | type: integer 1684 | example: 1585249190629 1685 | description: Timestamp of the expiration time of this session in milliseconds. 1686 | creation-time: 1687 | type: integer 1688 | example: 1585234790629 1689 | description: Timestamp of when this session was created in milliseconds. 1690 | uploaded-parts: 1691 | type: array 1692 | items: 1693 | type: object 1694 | properties: 1695 | offset: 1696 | type: integer 1697 | description: The offset of the chunk that was already uploaded from the beginning of the blob in bytes 1698 | example: 0 1699 | length: 1700 | type: integer 1701 | description: The size of the chunk that was already uploaded in bytes 1702 | example: 484173 1703 | description: The parts of this file that were already uploaded. Empty initially. 1704 | 1705 | BlobServerBlobMetadataResponse: 1706 | type: object 1707 | properties: 1708 | standard-metadata: 1709 | type: object 1710 | properties: 1711 | namespace-name: 1712 | type: string 1713 | example: 832DBA54-7E0B-463C-B0EF-DB65DAB25745 1714 | format: uuid 1715 | description: The namespace associated with the batch upload session that the committed upload session belongs to 1716 | blob-name: 1717 | type: string 1718 | example: /folder/file.png 1719 | description: The full path of the file that was uploaded 1720 | blob-id: 1721 | type: string 1722 | example: 33E36B98-2758-4C17-83E5-69E93B8B87CB 1723 | format: uuid 1724 | description: | 1725 | The ID of the uploaded blob. 1726 | The ID is a temporary, non-final identifier until `/blob-store-service/1.0/commit-batch-upload` is called. 1727 | The ID retured during `/blob-store-service/1.0/commit-upload` **will** change during 1728 | `/blob-store-service/1.0/commit-upload`! 1729 | metadata-revision: 1730 | type: string 1731 | format: number 1732 | example: "1" 1733 | description: The metadata revision number of the file that was uploaded. Edits to the file metadata increase this value. 1734 | content-revision: 1735 | type: string 1736 | format: number 1737 | example: "1" 1738 | description: The content revision number of the file that was uploaded. Edits to the contents of the file increase this value. 1739 | access: 1740 | type: string 1741 | example: opened 1742 | description: Describes access status 1743 | last-modified-by-user-name: 1744 | type: string 1745 | example: some-user 1746 | description: The username of the user who initiated the last edit to this file 1747 | last-modified-by-user-id: 1748 | type: string 1749 | format: uuid 1750 | example: d290f1ee-6c54-4b01-90e6-d701748f0852 1751 | description: The ID of the user who initiated the last edit to this file 1752 | last-modified: 1753 | type: string 1754 | format: number 1755 | example: "1585234790835" 1756 | description: The timestamp of the latest change made to this file in milliseconds 1757 | created-by-user-name: 1758 | type: string 1759 | example: some-user 1760 | description: The username of the user who initially uploaded this file 1761 | created-by-user-id: 1762 | type: string 1763 | format: uuid 1764 | example: d290f1ee-6c54-4b01-90e6-d701748f0852 1765 | description: The ID of the user who initially uploaded this file 1766 | created: 1767 | type: string 1768 | format: number 1769 | example: "1585234790835" 1770 | description: The timestamp of the initial upload of this file in milliseconds 1771 | content-disposition: 1772 | type: string 1773 | example: attachment; filename="/folder/file.png" 1774 | description: Content disposition of the file 1775 | content-language: 1776 | type: string 1777 | example: "" 1778 | description: Language of file contents 1779 | content-type: 1780 | type: string 1781 | example: application/octet-stream 1782 | description: Content type of this file 1783 | content-hash-algorithm: 1784 | type: string 1785 | example: SHA256 1786 | description: Name of the algorithm used for hashing the file content 1787 | content-hash: 1788 | type: string 1789 | example: E_UXOOjE-SDi-g_Tq6F7dQAd1dp-C5aLTIy1ThHFvFQ 1790 | description: The hash generated by the hash function described in content-hash-algorithm 1791 | cache-control: 1792 | type: string 1793 | example: no-cache 1794 | description: Describes cache 1795 | e-tag: 1796 | type: string 1797 | example: E_UXOOjE-SDi-g_Tq6F7dQAd1dp-C5aLTIy1ThHFvFQ 1798 | description: The e-tag associated with the file. 1799 | size: 1800 | type: string 1801 | format: number 1802 | example: "484173" 1803 | user-metadata: 1804 | type: object 1805 | description: Reserved for future use. 1806 | 1807 | GetBlobChangesForSyncResponseCreatedOrUpdatedObject: 1808 | type: object 1809 | properties: 1810 | id: 1811 | type: string 1812 | format: uuid 1813 | path: 1814 | type: string 1815 | example: Project Root/folder1/blob1.jpg 1816 | timestamp: 1817 | type: number 1818 | example: 8124389429384.234 1819 | revision: 1820 | type: integer 1821 | example: 1 1822 | 1823 | Job: 1824 | type: object 1825 | properties: 1826 | id: 1827 | type: string 1828 | format: uuid 1829 | authorId: 1830 | type: string 1831 | format: uuid 1832 | nullable: true 1833 | description: Identifier of the user who started the job. 1834 | isService: 1835 | type: boolean 1836 | description: Started by system (true), or by a user (false). 1837 | startedOn: 1838 | type: number 1839 | description: Start time (JavaScript timestamp). 1840 | progressedOn: 1841 | type: number 1842 | description: Last progress time (JavaScript timestamp). 1843 | status: 1844 | type: string 1845 | enum: ['starting', 'running', 'failed', 'completed', 'aborted', 'aborting', 'undoing', 'abort failed'] 1846 | description: > 1847 | Job status: 1848 | * `starting` - Job is about to get started. 1849 | * `running` - Job is running. 1850 | * `failed` - Job failed. 1851 | * `completed` - Job completed. 1852 | * `aborted` - Job aborted. 1853 | * `aborting` - Job has been aborted, and processing its abort state. Status will go to *aborted* or *abort failed* from there eventually. 1854 | * `undoing` - Job has been failed, and undoing its partially completed operation. Status will go to *failed* from there eventually. 1855 | * `abort failed` - Job aborted, but there was an error while doing its abort operation. 1856 | jobType: 1857 | type: string 1858 | example: destroyResources 1859 | description: Type of the job. 1860 | progress: 1861 | type: object 1862 | properties: 1863 | min: 1864 | type: number 1865 | example: 0 1866 | max: 1867 | type: number 1868 | example: 100 1869 | current: 1870 | type: number 1871 | example: 50 1872 | description: Job actual progress between *min* and *max*. 1873 | phase: 1874 | type: string 1875 | example: removingElements 1876 | description: Phase identifier of multi-phase jobs. Empty string if the job is single phased. 1877 | result: 1878 | type: string 1879 | nullable: true 1880 | description: | 1881 | Job dependent result value if *status* is *completed*. 1882 | 1883 | Error message if *status* is *failed* or *abort failed*. 1884 | resultCode: 1885 | type: string 1886 | nullable: true 1887 | description: | 1888 | Job dependent result code if *status* is *completed*, usually zero. 1889 | 1890 | Error code if *status* is *failed* or *abort failed*. 1891 | abortable: 1892 | type: boolean 1893 | description: | 1894 | If true, job supports abort operation. 1895 | 1896 | 1897 | # FS ERRORS 1898 | 1899 | BlobServerDetailedError: 1900 | type: object 1901 | properties: 1902 | data-content-type: 1903 | type: string 1904 | example: application/vnd.graphisoft.teamwork.generic-service-1.0.detailed-error-1.0+json 1905 | data: 1906 | type: object 1907 | properties: 1908 | error-message: 1909 | type: string 1910 | example: "Failed to execute request: 'session-service-create-session'. Error: 'Authentication failed: access control ticket is expired.'." 1911 | description: The error message generated by the Blob Server 1912 | error-code: 1913 | type: integer 1914 | example: 4 1915 | pattern: '[1-5]|1[1-9]|2[0-3]' 1916 | description: | 1917 | Error code-name pairs: 1918 | - `1` `GenericError` 1919 | - `2` `AuthenticationRequired` 1920 | - `3` `AuthenticationFailed` 1921 | - `4` `AccessControlTicketExpired` 1922 | - `5` `AccessDenied` 1923 | - `11` `SessionNotFound` 1924 | - `12` `BatchUploadCommitFailed` 1925 | - `13` `InvalidBlobContentPart` 1926 | - `14` `UploadSessionNotFound` 1927 | - `15` `IncompleteUpload` 1928 | - `16` `BlobAttachmentNotFound` 1929 | - `17` `BlobNamespaceNotFound` 1930 | - `18` `BlobRevisionNotFound` 1931 | - `19` `BlobChunkNotFound` 1932 | - `20` `BlobAlreadyExists` 1933 | - `21` `BlobNotFound` 1934 | - `22` `BlobAccessDenied` 1935 | - `23` `BlobPermissionDenied` 1936 | details: 1937 | type: object 1938 | properties: 1939 | message: 1940 | type: string 1941 | example: "Authentication failed: access control ticket is expired." 1942 | description: The error message generated by the Blob Server 1943 | reason: 1944 | type: string 1945 | example: AccessControlTicketExpired 1946 | description: The internal name of the error thrown. See the description of error-code for details. 1947 | 1948 | # PS ERRORS 1949 | 1950 | PortalServerError: 1951 | type: object 1952 | properties: 1953 | error-code: 1954 | type: integer 1955 | example: 6 1956 | description: | 1957 | The code of the error being thrown. Current error code-name pairs: 1958 | - `1` `GenericError` 1959 | - `2` `AuthenticationRequiredError` 1960 | - `3` `AccessDeniedError` 1961 | - `4` `EntityCyclicDependencyError` 1962 | - `5` `EntityExistsError` 1963 | - `6` `EntityNotFoundError` 1964 | - `7` `EntityValidationError` 1965 | - `8` `OptimisticLockError` 1966 | - `9` `RevisionObsoletedError` 1967 | - `10` `LdapConnectionError` 1968 | - `11` `LdapInvalidCredentialsError` 1969 | - `12` `FileConnectionBaseDnError` 1970 | - `13` `ModelServerSideError` 1971 | - `14` `ReferenceError` 1972 | - `15` `ProhibitDeleteError` 1973 | - `16` `LicenseManagerError` 1974 | - `17` `ResultLimitExceededError` 1975 | - `18` `ModelServerNotCompatibleError` 1976 | - `19` `NotEnoughFreeSpaceError` 1977 | - `20` `ChangeHostError` 1978 | - `21` `GSIDConnectionError` 1979 | - `22` `GSIDInvalidCredentialsError` 1980 | - `23` `TagAlreadyAssignedError` 1981 | - `24` `KeyExistsError` 1982 | - `25` `NotAllowedError` 1983 | - `26` `NotYetAvailableError` 1984 | - `27` `InsufficientLicenseError` 1985 | error-message: 1986 | type: string 1987 | example: 'EntityNotFoundError: No item found by "projectRootz"' 1988 | description: Details about why the error has occured 1989 | 1990 | # OAUTH SPECIFIC 1991 | OauthError: 1992 | type: object 1993 | properties: 1994 | error: 1995 | type: string 1996 | example: some_error 1997 | description: | 1998 | 'Error name. Possible values: `access_denied`, `insufficient_scope`, `invalid_argument`, `invalid_client`' 1999 | '`invalid_grant`, `invalid_request`, `invalid_scope`, `invalid_token`, `server_error`, `unauthorized_client`' 2000 | '`unauthorized_request`, `unsupported_grant_type`, `unsupported_response_type`' 2001 | error_description: 2002 | type: string 2003 | example: Some error happened 2004 | description: Additional error information 2005 | inner: 2006 | description: Optional inner error object 2007 | properties: 2008 | error-code: 2009 | type: number 2010 | example: 911 2011 | error-message: 2012 | type: string 2013 | example: Some error happened 2014 | 2015 | GetTokenRequest: 2016 | type: object 2017 | properties: 2018 | grant_type: 2019 | type: string 2020 | description: '`refresh_token` or `authorization_code`' 2021 | example: authorization_code 2022 | client-id: 2023 | type: string 2024 | description: 'Client identification' 2025 | example: example.com 2026 | code: 2027 | type: string 2028 | description: 'The authorization code obtained from redirect_uri or the poll api. Required for `authorization_code` grant type.' 2029 | example: authorization_code 2030 | redirect_uri: 2031 | type: string 2032 | description: 'The redirect URI. Only for `authorization_code` grant type. Only required if provided in the GET /authorize request and only the same URI is allowed.' 2033 | example: https://example.com 2034 | refresh_token: 2035 | type: string 2036 | description: 'The refresh token. Only for `refresh_token` grant type.' 2037 | example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb25uZWN0aW9uSWQiOiI1OGIyMzVkYS0zMmRkLTE3OGItMjZiNi1lNjA0ODg1YzZmYWUiLCJ1c2VySWQiOiJ0dTEiLCJjbGllbnRJZCI6ImdyYXBoaXNvZnQuY29tIiwicGxhdGZvcm0iOiJ3ZWJVSSIsImV4cCI6MTY3MTAyMzM4MywianRpIjoiNGUzYTEzZTYtYWExNC01YWZhLTBlMjQtY2E2YThlNmRmNmNiIiwiaWF0IjoxNjcxMDIzMzczLCJ0eXBlIjoicmVmcmVzaCJ9.aBzGe3Ce_dmbON67Fs001vSU6H7yEcTlUHMBZHqq9bO0ryA0KKwjHGgZRLNL9S-qBw8obeFqWZ3IsqBGyneY7o--Qxa-jmS4iKBxqValYNOzIAQFVd9PKMWbkyKX204KIAPck5sGWZcRvyNocHluzrimmExhxhWTZ_Y_WU5r8pk 2038 | code_verifier: 2039 | type: string 2040 | description: PKCE code verifier. Must be the same as the encoded string in code_challenge. Only for `authorization_code` grant type. 2041 | example: eaowlwoarpnxszyclnpkuckdynyfoyhsrvpfoavblct 2042 | 2043 | GetTokenResponse: 2044 | type: object 2045 | properties: 2046 | access_token: 2047 | type: string 2048 | example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiJ0dTEiLCJjbGllbnRJZCI6ImdyYXBoaXNvZnQuY29tIiwicGxhdGZvcm0iOiJ3ZWJVSSIsImV4cCI6MTY3MTAyMzM4MywianRpIjoiMWI5NWI4MWEtZWNjNC0zODYzLTQwNjYtNzc0NzExYjQ4ZGRhIiwiaWF0IjoxNjcxMDIzMzczLCJ0eXBlIjoiYWNjZXNzIn0.GO8vaTNPMeq8GZEU7bAvj6zWNyzt_a1korLhEbdjhsdhm834jBg1mDEQfqpRUq2MwaMQNUb3EyKKj4bxni72UPXPSaDN4mecinqHXqO6sK6gtOF_wAVqV8Z701FfyACors5iwlBARcJZmhcqSMNCwTrVho8FTv8eHXZm9yFf_BQ 2049 | description: The access token (JWT) 2050 | access_token_exp: 2051 | type: number 2052 | example: 1671023383 2053 | description: The expiration time of the access token (UNIX timestamp) 2054 | refresh_token: 2055 | type: string 2056 | example: eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJjb25uZWN0aW9uSWQiOiI1OGIyMzVkYS0zMmRkLTE3OGItMjZiNi1lNjA0ODg1YzZmYWUiLCJ1c2VySWQiOiJ0dTEiLCJjbGllbnRJZCI6ImdyYXBoaXNvZnQuY29tIiwicGxhdGZvcm0iOiJ3ZWJVSSIsImV4cCI6MTY3MTAyMzM4MywianRpIjoiNGUzYTEzZTYtYWExNC01YWZhLTBlMjQtY2E2YThlNmRmNmNiIiwiaWF0IjoxNjcxMDIzMzczLCJ0eXBlIjoicmVmcmVzaCJ9.aBzGe3Ce_dmbON67Fs001vSU6H7yEcTlUHMBZHqq9bO0ryA0KKwjHGgZRLNL9S-qBw8obeFqWZ3IsqBGyneY7o--Qxa-jmS4iKBxqValYNOzIAQFVd9PKMWbkyKX204KIAPck5sGWZcRvyNocHluzrimmExhxhWTZ_Y_WU5r8pk 2057 | description: The refresh token (JWT) 2058 | token_type: 2059 | type: string 2060 | example: Bearer 2061 | description: The type of the token issued. Only `Bearer` is supported. 2062 | user_id: 2063 | type: string 2064 | example: 8d597df5-1a4b-44c5-8f39-73173092947d 2065 | description: The id of the user to whom the token was issued 2066 | 2067 | GetAuthenticationCodeByStateResponse: 2068 | type: object 2069 | properties: 2070 | status: 2071 | type: string 2072 | description: '`succeeded` or `pending`' 2073 | example: succeeded 2074 | code: 2075 | type: string 2076 | description: 'The authorization code generated or `null`, if pending' 2077 | example: 8d597df5-1a4b-44c5-8f39-73173092947d --------------------------------------------------------------------------------