For the complete documentation index, see llms.txt. This page is also available as Markdown.

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.

Flow
How it works
Use it when

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.

Map metadata

Both flows start with POST /v2/vps/map and take the same body.

Field
Type
Required
Notes

mapName

string

yes

Display name for the map.

fileSize

number

yes

Total upload size in bytes. Must be greater than 0.

partSize

integer

no

Part size in bytes. Recommended for resumable uploads, see Choosing a part size.

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:

Field
Accepted values

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

For a Matterport or NavVis E57 scan:

Upload each part with the Content-Type that matches your fileType: application/zip for a zip upload, application/octet-stream otherwise. Presigned URLs are valid for 1 hour.

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.

Create Map & Start Upload

post

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.

Authorizations
AuthorizationstringRequired

JWT token obtained from the authentication endpoint.

Query parameters
resumablebooleanOptional

Set true for resumable mode (sign parts on demand).

Body
mapNamestringRequiredExample: Ground Floor
fileSizeintegerRequired

Total upload size in bytes (must be > 0).

Example: 734003200
partSizeintegerOptional

Optional part size in bytes. When provided, the upload is verified complete before finalizing.

Example: 8388608
headingnumber · max: 360Optional

Optional compass heading in degrees.

Responses
201

Map created and upload initialised.

application/json
messagestringOptionalExample: Map created successfully
mapCodestringOptionalExample: MAP_QQYKIBHXZE01
mapIdstringOptional
uploadUrlsstring[]Optional

Presigned part URLs (standard flow only; omitted in resumable mode).

post/map
POST /v2/vps/map HTTP/1.1
Host: api.multiset.ai
Authorization: Bearer YOUR_SECRET_TOKEN
Content-Type: application/json
Accept: */*
Content-Length: 231

{
  "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": {}
  }
}
{
  "message": "Map created successfully",
  "mapCode": "MAP_QQYKIBHXZE01",
  "mapId": "text",
  "uploadUrls": [
    "text"
  ]
}

Complete Upload

post

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).

Authorizations
AuthorizationstringRequired

JWT token obtained from the authentication endpoint.

Path parameters
idstringRequired
Body
Responses
200

Upload completed; map queued for processing.

application/json
messagestringOptionalExample: Map uploaded successfully
mapIdstringOptional
post/map/complete-upload/{id}
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.

Sign Upload Parts

post

Returns presigned URLs for the requested part numbers of an in-progress upload (resumable flow).

Authorizations
AuthorizationstringRequired

JWT token obtained from the authentication endpoint.

Body
mapIdstringRequired
partNumbersinteger[] · min: 1 · max: 100Required

1–100 unique part numbers to sign.

Example: [1,2,3,4]
Responses
200

Signed URLs keyed by part number.

application/json
post/map/sign-part
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"
  }
}

List Uploaded Parts

get

Lists the part numbers already received by the server, so a resumed upload can skip them.

Authorizations
AuthorizationstringRequired

JWT token obtained from the authentication endpoint.

Path parameters
idstringRequired

Map id.

Responses
200

Uploaded parts.

application/json
activebooleanOptionalExample: true
uploadedPartNumbersinteger[]OptionalExample: [1,2,3,5]
get/map/list-parts/{id}
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
  ]
}

Abort Upload

post

Cancels an in-progress upload and removes the map.

Authorizations
AuthorizationstringRequired

JWT token obtained from the authentication endpoint.

Path parameters
idstringRequired
Responses
200

Upload aborted.

application/json
messagestringOptionalExample: Upload aborted
mapIdstringOptional
post/map/abort-upload/{id}
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

Status
Meaning

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.

The upload location is managed for you. You work with the mapId and the presigned URLs, and never construct a storage path yourself.

Last updated

Was this helpful?