Map Upload
Map files are uploaded to S3 in parts, so a large scan can be sent as parallel chunks rather than one request. The process supports files up to 25 GB, with retries on individual parts.
There are two ways to run an upload. Both use the same map metadata and the same PUT per part, and both finish with the same call.
Standard
The create call returns a presigned URL for every part up front.
The upload is small, or the connection is reliable.
Resumable
Part URLs are signed on demand, and an interrupted upload can be continued instead of restarted.
Large scans, slow or unstable connections. Recommended for big files.
If you are unsure, use the resumable flow. It behaves the same as the standard flow on a clean run, and it saves re-uploading everything if the connection drops.
Map metadata
Both flows start with POST /v2/vps/map and take the same body.
mapName
string
yes
Display name for the map.
fileSize
number
yes
Total upload size in bytes. Must be greater than 0.
coordinates.latitude
number
yes
-90 to 90.
coordinates.longitude
number
yes
-180 to 180.
coordinates.altitude
number
yes
Metres.
heading
number
no
0 to 360 degrees.
source
object
no
Describes the scan, see below. Defaults to a zip upload.
The source object
Set source to match the scan you are uploading:
provider
unity, matterport, leica, navvis, xgrid, faro, insta360
fileType
zip, e57, splat, 360 (defaults to zip)
coordinateSystem
LHS, RHS, RHS-Z-UP, RHS-Y-UP
metadata.assets
Optional deliverables to build alongside the map, see Optional assets
For a Matterport or NavVis E57 scan:
Optional assets
Some deliverables are not built by default. Request them with source.metadata.assets, an array of the extras you want:
pano
A navigable 360° Virtual Tour built from the panoramic imagery in the capture
360 video captures, e57 and MatterPak scans
Omit the field, or send an empty array, and no extras are built. The mesh and point cloud are always produced, so the map itself is unaffected either way.
assets is read once, at map creation. It cannot be added to an existing map. If you upload an .e57 or MatterPak scan without assets: ["pano"] and later want a tour, you have to create a new map and upload the scan again.
Standard upload
1. Create the map
POST /v2/vps/map with the metadata above. The response includes a presigned URL for every part:
2. Upload the parts
Split the file and PUT each chunk to its matching signedUrl. Each successful PUT returns an ETag header. Keep every ETag with its part number, you need them to finish the upload.
3. Complete the upload
POST /v2/vps/map/complete-upload/{mapId} with the parts you uploaded:
The parts are assembled into a single file and the map enters the processing queue. Once processing finishes, the map is available for VPS queries.
Creates the map and initialises a multipart upload. In the standard
flow the response includes presigned URLs for every part. In the
resumable flow (?resumable=true) the response returns only the
mapId/mapCode; request part URLs on demand with Sign Upload Parts.
JWT token obtained from the authentication endpoint.
Set true for resumable mode (sign parts on demand).
Ground FloorTotal upload size in bytes (must be > 0).
734003200Optional part size in bytes. When provided, the upload is verified complete before finalizing.
8388608Optional compass heading in degrees.
Map created and upload initialised.
Map created successfullyMAP_QQYKIBHXZE01Presigned part URLs (standard flow only; omitted in resumable mode).
Bad Request. Missing/invalid fields, maps limit reached, or plan expired.
Unauthorized. Invalid or expired token.
POST /v2/vps/map HTTP/1.1
Host: api.multiset.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 248
{
"mapName": "Ground Floor",
"fileSize": 734003200,
"partSize": 8388608,
"heading": 1,
"coordinates": {
"latitude": 12.997,
"longitude": 77.7679,
"altitude": 0
},
"source": {
"provider": "unity",
"fileType": "zip",
"coordinateSystem": "LHS",
"metadata": {
"assets": [
"pano"
]
}
}
}{
"message": "Map created successfully",
"mapCode": "MAP_QQYKIBHXZE01",
"mapId": "text",
"uploadUrls": [
"text"
]
}Finalizes the multipart upload and queues the map for processing. In the
resumable flow send an empty body {}, the server assembles the
upload from the parts it received. In the standard flow send the
parts array (each part's ETag and PartNumber).
JWT token obtained from the authentication endpoint.
Upload completed; map queued for processing.
Map uploaded successfullyUnauthorized.
No active upload
POST /v2/vps/map/complete-upload/{id} HTTP/1.1
Host: api.multiset.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 42
{
"parts": [
{
"ETag": "text",
"PartNumber": 1
}
]
}{
"message": "Map uploaded successfully",
"mapId": "text"
}Resumable upload
1. Create the map in resumable mode
POST /v2/vps/map?resumable=true with the same metadata. Include partSize so the server can confirm the upload is complete before finalizing it.
The response is deliberately small, because no part URLs are issued yet:
2. Request URLs for the parts you are about to send
POST /v2/vps/map/sign-part with the mapId and the part numbers you want, up to 100 per call:
3. Upload the parts
PUT each chunk to its signedUrl, exactly as in the standard flow. You do not need to keep the ETag values here, because the server reads the uploaded parts when you complete.
4. Resume after an interruption
Ask which parts already arrived:
GET /v2/vps/map/list-parts/{mapId}
Skip those, request fresh URLs for the rest with sign-part, and upload only what is missing. Repeat as often as you need, the upload stays open.
5. Complete the upload
POST /v2/vps/map/complete-upload/{mapId} with an empty body:
The server assembles the upload from the parts it received. If you supplied partSize when creating the map, it first checks that every expected part is present and returns 409 if any are missing.
Cancel an upload
POST /v2/vps/map/abort-upload/{mapId} discards the upload and removes the map.
Returns presigned URLs for the requested part numbers of an in-progress upload (resumable flow).
JWT token obtained from the authentication endpoint.
1–100 unique part numbers to sign.
[1,2,3,4]Signed URLs keyed by part number.
Unauthorized.
Forbidden. The map does not belong to your account.
Map not found.
No active upload for this map.
POST /v2/vps/map/sign-part HTTP/1.1
Host: api.multiset.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 40
{
"mapId": "text",
"partNumbers": [
1,
2,
3,
4
]
}{
"signedUrls": {
"ANY_ADDITIONAL_PROPERTY": "text"
}
}Lists the part numbers already received by the server, so a resumed upload can skip them.
JWT token obtained from the authentication endpoint.
Map id.
Uploaded parts.
true[1,2,3,5]Unauthorized.
Forbidden.
Map not found.
GET /v2/vps/map/list-parts/{id} HTTP/1.1
Host: api.multiset.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"active": true,
"uploadedPartNumbers": [
1,
2,
3,
5
]
}Cancels an in-progress upload and removes the map.
JWT token obtained from the authentication endpoint.
Upload aborted.
Upload abortedUnauthorized.
Forbidden.
Map not found.
No in-progress upload to abort for this map.
POST /v2/vps/map/abort-upload/{id} HTTP/1.1
Host: api.multiset.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Accept: */*
{
"message": "Upload aborted",
"mapId": "text"
}Choosing a part size
partSize controls how the file is split. A smaller part means more requests but less to re-send after a failure. A larger part means fewer requests but more lost work when one fails.
8 MB to 16 MB suits most uploads on a normal connection.
Go smaller, around 5 MB, on unstable or mobile connections so a dropped part costs little.
The number of parts works out to
ceil(fileSize / partSize).
Errors worth handling
400
Missing or invalid metadata, maps limit reached, or the plan has expired.
403
The map belongs to another account.
409
No active upload for this map, or the upload is incomplete because expected parts are missing.
Last updated
Was this helpful?

