Skip to content

Shared Multipart Upload Reference

Several RealityConnect API upload endpoints share the same underlying pattern: initiate the upload, PUT the file directly to one or more presigned URLs, then finalize it. This page is the shared reference for that pattern, its part-numbering rule, and how to refresh, resume, and monitor an upload. Link here from any endpoint-specific guide instead of repeating the mechanic.


FlowInitiateFinalizeChunked into parts?Refresh endpoint?
Data bundle uploadsPOST /v1/bundles/{bundleId}/upload-sessions/{sessionId}/filesPOST .../files/{fileId}/finalizeYesYes
Object attachmentsPOST /v1/twin/{contextId}/object/attachmentsPOST .../attachments/{fileId}/finalizeYesYes
Site filesPOST /v1/site-filesPOST /v1/site-files/{fileId}/finalizeYesNo
Asset library modelsPOST /v1/asset-library/{ownerContextId}/modelsPOST .../models/{libraryModelId}/finalizeNo (single file)No
Node thumbnailsPOST /v1/nodes/{nodeId}/thumbnailPOST /v1/nodes/{nodeId}/thumbnail/{uploadId}/finalizeNo (single file)No
sequenceDiagram
  participant Client as Client
  participant RCAPI as RCAPI
  participant S3 as S3

  Client->>RCAPI: POST initiate
  RCAPI-->>Client: fileId/uploadId + presigned URL(s)
  loop for each part
    Client->>S3: PUT part
    S3-->>Client: ETag
  end
  Client->>RCAPI: POST finalize (with ETags)
  RCAPI-->>Client: upload completed
  1. Initiate: call the flow’s initiate endpoint with the file’s name and size (and any flow-specific fields, such as a bundle file type). The response returns an identifier to finalize with, and one or more presigned S3 urls to PUT the file to.
  2. Upload: PUT the file’s bytes directly to the returned URL(s). No Authorization header is sent on these requests; the URL is already signed. Keep the ETag response header from every PUT.
  3. Finalize: call the flow’s finalize endpoint with the collected ETag(s) to complete the upload.

Asset library models and node thumbnails currently accept only a single file in one PUT, so there is one URL, one ETag, and no partNumber at all. Treat those two as the simple, one-part case of the same shape.

This applies to the two flows that support refresh.

Worked example. Initiate returns 3 parts:

{
"urls": [
{ "partNumber": 1, "url": "https://s3…/part-1?…" },
{ "partNumber": 2, "url": "https://s3…/part-2?…" },
{ "partNumber": 3, "url": "https://s3…/part-3?…" }
]
}

To refresh starting from partNumber: 2, subtract 1 and call refresh with startIndex = 1:

GET {api_url}/.../refresh/1

The response’s startIndex echoes back 1, and its urls again carry 1-based partNumbers starting at 2:

{ "startIndex": 1, "urls": [ { "partNumber": 2, "url": "…" }, { "partNumber": 3, "url": "…" } ] }

Passing partNumber unchanged (2 instead of 1) as startIndex would skip part 2 and only refresh part 3 onward.

Presigned PUT URLs expire. If an upload runs long and a part’s PUT starts returning 403, call the refresh endpoint for the part you stopped at (as startIndex = partNumber - 1). Refresh returns fresh URLs for that part and every part after it, so you do not need to refresh each part individually, and the file does not need to be re-initiated.

Refresh is available for:

  • Data bundle uploads: GET /v1/bundles/{bundleId}/upload-sessions/{sessionId}/files/{fileId}/refresh/{startIndex}
  • Object attachments: GET /v1/twin/{contextId}/object/attachments/{fileId}/refresh/{startIndex}

Site files, asset library models, and node thumbnails have no refresh endpoint. If a URL expires mid-upload on one of those flows, re-initiate the file to obtain a fresh URL.

Resuming a partial upload and tracking progress

Section titled “Resuming a partial upload and tracking progress”

How you resume, and how you check progress, depends on the flow:

  • Data bundle uploads track progress at the session level. List a bundle’s upload sessions (GET /v1/bundles/{bundleId}/upload-sessions) and compare finalizedFileCount to expectedFileCount. To resume, initiate and finalize only the files that have not completed yet, using the type and fileName you have not seen appear as finalized.
  • Object attachments, site files, asset library models, and node thumbnails are single-file operations with no session or list to poll. Progress is simply whether finalize has succeeded for the fileId (or uploadId) you were given. Asset library models additionally expose this through the model’s own status field, which reads uploading until finalize completes. To resume any of these after an interruption, keep the identifier from initiate and either continue uploading its remaining parts (refreshing URLs as needed, where available) or re-initiate if the identifier or its URLs can no longer be used.

Storage limits and re-uploading an existing file

Section titled “Storage limits and re-uploading an existing file”

Initiating an upload can be rejected before any bytes are sent if the request itself is invalid, most commonly StorageLimitExceeded when the organization’s storage quota is already exceeded, or FileAlreadyExists when re-uploading over a file that already exists under the same identity. These are named error codes in the response body, catalogued with their exact status codes per endpoint in Error Codes: File uploads.

  • Error Codes for the full list of named codes, including StorageLimitExceeded and FileAlreadyExists per endpoint.
  • Uploading Data to a Data Bundle for the fullest worked example of this pattern, including a resumable batch upload.