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.
Which endpoints use this pattern
Section titled “Which endpoints use this pattern”| Flow | Initiate | Finalize | Chunked into parts? | Refresh endpoint? |
|---|---|---|---|---|
| Data bundle uploads | POST /v1/bundles/{bundleId}/upload-sessions/{sessionId}/files | POST .../files/{fileId}/finalize | Yes | Yes |
| Object attachments | POST /v1/twin/{contextId}/object/attachments | POST .../attachments/{fileId}/finalize | Yes | Yes |
| Site files | POST /v1/site-files | POST /v1/site-files/{fileId}/finalize | Yes | No |
| Asset library models | POST /v1/asset-library/{ownerContextId}/models | POST .../models/{libraryModelId}/finalize | No (single file) | No |
| Node thumbnails | POST /v1/nodes/{nodeId}/thumbnail | POST /v1/nodes/{nodeId}/thumbnail/{uploadId}/finalize | No (single file) | No |
The initiate, upload, finalize shape
Section titled “The initiate, upload, finalize shape”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
- 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 S3urls toPUTthe file to. - Upload:
PUTthe file’s bytes directly to the returned URL(s). NoAuthorizationheader is sent on these requests; the URL is already signed. Keep theETagresponse header from everyPUT. - 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.
Part numbering: partNumber vs startIndex
Section titled “Part numbering: partNumber vs startIndex”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/1The 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.
Refreshing expired signed URLs mid-upload
Section titled “Refreshing expired signed URLs mid-upload”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 comparefinalizedFileCounttoexpectedFileCount. To resume, initiate and finalize only the files that have not completed yet, using thetypeandfileNameyou 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(oruploadId) you were given. Asset library models additionally expose this through the model’s ownstatusfield, which readsuploadinguntil 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.
What’s next?
Section titled “What’s next?”- Error Codes for the full list of named codes, including
StorageLimitExceededandFileAlreadyExistsper endpoint. - Uploading Data to a Data Bundle for the fullest worked example of this pattern, including a resumable batch upload.