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
- It is possible to download multiple artifacts with the
- 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 Case | File | Artifact |
---|---|---|
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