Features

Creating and Uploading an Asset

GraphQL flow chart:

Note: The parts annotated with ** are under development at the time of writing and might not be available in production yet.

1. Creating the Asset Entity

Replace [FOLDER_ID] and [FILE_NAME] to create an asset entity.

mutation {
    createAssetV3(
        params: {
            folderId: "[FOLDER_ID]",
            name: "[FILE_NAME]"
        }
    ) {
        __typename
        ... on GroupedAssetOutput {
            id
        }
        ... on BaseErrorInterface {
            message
        }
    }
}

Attach the bearer token to the GraphQL headers:

{
   "authorization": "Bearer eyJraWQiOiJydGdqUUxXZTBqbExIWHVBUm8zR1lP….."
}

Response:

{
    "data": {
        "createAssetV3": {
            "__typename": "GroupedAssetOutput",
            "id": "f5fa9a0d-7479-49bf-8962-4fab70b89de9"
        }
    }
}

2. Choosing Between Files and Artifacts

Files are slowly being replaced by artifacts, however, not all functionality supported by files is available for artifacts.

The following file functionality is currently not yet supported by artifacts:

  • Artifacts are not counted towards ProjectOutput.projectSize
  • Artifacts are not included in (multi) asset / folder downloads
    • It is possible to download multiple artifacts with the multiArtifactDownloadLink query
  • Some processing pipelines can only be triggered on files, e.g., automatically triggered pipelines on file upload

To determine whether to upload data as a file or artifact, consult the following table:

Use CaseFileArtifact
Generic single file (e.g. pdf, txt) **
Generic multi-file with index* **
Streamable data formats (e.g. OGC3dTiles, HSPC)
Any file that requires automated processing on upload (e.g. e57)
Any file that must be included in (multi) asset / folder downloads

* An asset with multiple files that doesn’t contain an index file should create a new artifact per file. Alternatively, consider creating multiple assets.

3. Creating and Uploading a File

Files represent the actual data being uploaded to an asset. Assets can have multiple files associated with them.

3a. Creating the File Entity

Replace [GROUPED_ASSET_ID] (e.g. “f5fa9a0d-7479-49bf-8962-4fab70b89de9” from the first step), [FILE_NAME] and [FILE_SIZE] to create a file entity, by using the ID from the GroupedAssetOutput. Repeat this process for every file associated with a single asset.

mutation {
    addFileV2(
        params: {
            groupedAssetId: "[GROUPED_ASSET_ID]",
            fileName: "[FILE_NAME]",
            fileSize: [FILE_SIZE]
        }
    ) {
        __typename
        ... on FileOutput {
            id
        }
        ... on BaseErrorInterface {
            message
        }
    }
}

Response:

{
    "data": {
        "addFileV2": {
            "__typename": "FileOutput",
            "id": "3126a9ae-9ae0-498b-83c1-524249b523f3"
        }
    }
}

3b. Generating a Presigned URL for the File

Split the file into parts and replace the [PART_NUMBER] and [FILE_ID] (e.g. “3126a9ae-9ae0-498b-83c1-524249b523f3” from the previous step) to generate a presigned url to upload the file in parts. Repeat this step for all parts.

query {
    multipartUploadURL(
        params: {
            partNumber: [PART_NUMBER],
            fileId: "[FILE_ID]"
        }
    ) {
        __typename
        ... on MultipartUploadUrlOutput {
            uploadUrl
        }
        ... on BaseErrorInterface {
            message
        }
    }
}

Response:

{
    "data": {
        "multipartUploadURL": {
            "__typename": "MultipartUploadUrlOutput",
            "uploadUrl": "https://s3.[AWS_REGION].amazonaws.com/[HXDR_ENVIRONMENT]/assets/[ASSET_ID]/[FILE_NAME]?uploadId=[UPLOAD_ID]&partNumber=[PART_NUMBER]&X-Amz-Security-Token=[SECURITY_TOKEN]&X-Amz-Algorithm=[ALGORITHM]&X-Amz-Date=[DATE]&X-Amz-SignedHeaders=[SIGNED_HEADERS]&X-Amz-Expires=[EXPIRATION]&X-Amz-Credential=[CREDENTIALS]&X-Amz-Signature=[SIGNATURE]"
        }
    }
}

Note: Nothing should be replaced in this response. It has been shortened to hide any real data.

The uploadUrl is the place to PUT the part. This is a direct connection to AWS S3. The etag is located in the header of a successful response of an uploaded part which is needed for the next step.

To upload the data via the presigned url via a terminal, the following command can be used

curl --request PUT --url "[UPLOAD_URL]" --verbose --upload-file example.file

3c. Completing a File

To complete a file, replace [FILE_ID], [PART_NUMBER], [ETAG], and [LIST_OF_PART_OBJECTS]. Repeat this step for every file (not part!) that has been uploaded.

mutation {
    completeMultipartUpload(
        params: {
            fileId: "[FILE_ID]",
            multipartUploadsETags: [LIST_OF_PART_OBJECTS]
        }
    ) {
        __typename
        ... on ExecutedOutput {
            executed
        }
        ... on BaseErrorInterface {
            message
        }
    }
}

Where [LIST_OF_PART_OBJECTS] is an array where each object has the following fields:

{
    "part": [PART_NUMBER],
    "etag": "[ETAG]"
}

Example:

mutation {
    completeMultipartUpload(
        params: {
            fileId: "3126a9ae-9ae0-498b-83c1-524249b523f3",
            multipartUploadsETags: [{
                part: 1,
                etag: "etag_1"
            },
            ...,
            {
                part: n,
                etag: "etag_n"
            }]
        }
    ) {
        __typename
        ... on ExecutedOutput{
            executed
        }
        ... on BaseErrorInterface {
            message
        }
    }
}

Response:

{
    "data": {
        "completeMultipartUpload": {
            "__typename": "ExecutedOutput",
            "executed": true
        }
    }
}

3d. Completing an Asset

Replace [GROUPED_ASSET_ID] with the ID from step 1. After all files have been uploaded and completed, the asset can be processed. This happens when triggering completeAssetFileList (currently this only affects assets with files).

mutation {
    completeAssetFileList(
        groupedAssetId: {
            assetId: "[GROUPED_ASSET_ID]"
        }
    ) {
        __typename
        ... on ExecutedOutput {
            executed
            message
        }
        ... on BaseErrorInterface {
            message
        }
    }
}

Response:

{
    "data": {
        "completeAssetFileList": {
            "__typename": "ExecutedOutput",
            "executed": true,
            "message": "Asset Upload is completed"
        }
    }
}

Once all of the above steps have been completed, the automatic triggering of a specific pipeline will happen. The type of pipeline triggered depends on the assetType uploaded or the file extension of the uploaded file.

4. Creating and Uploading an Artifact

Files can also be uploaded as artifacts to an existing asset. The main difference between artifacts and files is that files always represent a single underlying file, while artifacts can represent either a single file, or a collection of structured files. In case an artifact is a singular file, the address will point directly to the file. If the artifact contains multiple files, the address will point to the index file. The path of the other files in the artifact must then be constructed based on the index file.

4a. Creating the Artifact Entity

Replace [GROUPED_ASSET_ID] with the ID from the GroupedAssetOutput from the first step. Replace [DATA_CATEGORY] with the appropriate data category (e.g. GENERIC_SINGLE_FILE). Replace [SAVED_FORMAT] with the appropriate saved format (e.g. UNSPECIFIED). To best be able to use the features of the platform, always try to specify the data category and saved format as accurately as possible, only use generic single file and unspecified if there is no good match. Repeat this process for every artifact associated with a single asset.

mutation {
    createArtifact(params: {
        groupedAssetId: "[GROUPED_ASSET_ID]",
        dataCategory: [DATA_CATEGORY],
        savedFormat: [SAVED_FORMAT]
    }) {
        __typename
        ... on ArtifactOutputV2 {
            id
        }
        ... on BaseErrorInterface {
            message
        }
    }
}

Response:

{
    "data": {
        "createArtifact": {
            "__typename": "ArtifactOutputV2",
            "id": "2d8083f8-9794-4538-95e3-bb64e40f8d2c"
        }
    }
}

Media Types **

Media types, or MIME types, can be specified when creating an artifact. This can be useful for the client (especially in a browser) to visualize the artifact. To specify a media type, add the mediaType field to the createArtifact mutation.

If the client knows the mime type, it should always specify it. If the mime type is not known, the backend will attempt to determine it based on the file, but there are no guarantees on the accuracy.

Index file of multi-file artifacts **

The following multi-file artifacts automatically assign the index file:

  • HSPC: tree.hspc
  • HSPC_PACK: tree.hspc.pack
  • OGC3D_TILES: tileset.json
  • LUCIAD_PANORAMIC: cubemap.json
  • LWPO: lwpo.zip
  • DBX: dbx.zip

The client must specify the index file of any other multi-file artifact.

4b. Generating a Presigned URL for the Artifact

Any amount of individual files can be uploaded to an artifact. To upload a single file to an artifact, replace [GROUPED_ASSET_ID] with the ID from the GroupedAssetOutput from the first step, [ARTIFACT_ID] with the ID from the ArtifactOutputV2 from the previous step and [FILE_NAME] with the name of the file to be uploaded. This must be unique within the artifact. When uploading a file in multiple parts, specify the [FILE_PART] (must be between 1 and 10 000) and [PART_SIZE] with the size of the part in bytes. filePart may be omitted when uploading a single part file.

This mutation can also be used to replace existing files in the artifact.

mutation {
    patchArtifact(params: {
        groupedAssetId: "[GROUPED_ASSET_ID]",
        artifactId: "[ARTIFACT_ID]",
        filename: "[FILE_NAME]",
        filePart: [FILE_PART],
        partSize: [PART_SIZE]
    }) {
        __typename
        ... on PatchArtifactOutput {
            url
        }
        ... on BaseErrorInterface {
            message
        }
    }
}

Response:

{
    "data": {
        "patchArtifact": {
            "__typename": "PatchArtifactOutput",
            "url": "https://[ENV].s3.eu-west-1.amazonaws.com/assets/03ab0c2e-5235-41f1-a398-6a4fe91b30c0/artifacts/2d8083f8-9794-4538-95e3-bb64e40f8d2c/GENERIC_SINGLE_FILE/UNSPECIFIED/exampleName?partNumber=0&uploadId=wFOi[...]&X-Amz-Security-Token=IQoJb[...]&X-Amz-Algorithm=AWS4[...]&X-Amz-Date=20250829T153154Z&X-Amz-SignedHeaders=host&X-Amz-Credential=ASIA[...]&X-Amz-Expires=3600&X-Amz-Signature=e53[...]"
        }
    }
}

Note: Nothing should be replaced in this url. It has been shortened to hide any real data.

The url is where you PUT the part. This is a direct connection to AWS S3. The etag is located in the header of a successful response from an uploaded part, which is needed for the next step.

To upload the data via the presigned URL using a terminal, the following command can be used:

curl --request PUT --url "[UPLOAD_URL]" --verbose --upload-file example.file

Example response:

* We are completely uploaded and fine
< HTTP/1.1 200 OK
< x-amz-id-2: zkH2H[...]
< x-amz-request-id: E0BZ[...]
< Date: Mon, 01 Sep 2025 08:10:53 GMT
< ETag: "0bee8[...]"
< x-amz-server-side-encryption: AES256
< Content-Length: 0
< Server: AmazonS3

4c. Completing an Artifact

Every file that was uploaded to an artifact must be completed. Replace [GROUPED_ASSET_ID] with the ID from step 1, [ARTIFACT_ID] with the ID from the ArtifactOutputV2 from step 4a, [FILE_NAME] with the name of the file to be completed, which was also used as input in the previous step, [FILE_PART] with the part number of the file to be completed (omit if the file was uploaded in a single part), and [ETAG] with the etag from the upload response of the part.

mutation {
    completeUpload(params: {
        groupedAssetId: "[GROUPED_ASSET_ID]",
        artifactId: "[ARTIFACT_ID]",
        filename: "[FILE_NAME]",
        parts: {
            filePart: [FILE_PART],
            etag: "[ETAG]"
        }
    }) {
        __typename
        ... on CompleteUploadOutput {
            success
        }
        ... on BaseErrorInterface {
            message
        }
    }
}

Example response:

{
    "data": {
        "completeUpload": {
            "__typename": "CompleteUploadOutput",
            "success": true
        }
    }
}

JS-SDK Reference

  • Assets

    HxDR Assets