This is the full developer documentation for MASV Developer Platform
# MASV Agent
> Cross-platform CLI and local REST API for automated, headless file transfers to and from the MASV network.
MASV Agent is a cross-platform service for automated file transfers. It runs as a local server with both a CLI and a REST API, handling uploads, downloads, automations, and cloud storage connections without a graphical interface.
## Guides
[Section titled “Guides”](#guides)
* **[Getting Started](/agent/getting-started/)** — Install and run MASV Agent on Linux, macOS, Windows, or Docker.
* **[Authentication](/agent/authentication/)** — Authenticate with API keys or credentials.
* **[Uploads](/agent/uploads/)** — Send files via Team email, shareable Link, or Portal.
* **[Downloads](/agent/downloads/)** — Initiate, monitor, and manage file downloads.
* **[Automations](/agent/automations/)** — Set up watch folders, Portal downloads, and stream uploads.
* **[Storage Gateway](/agent/storage-gateway/)** — Connect cloud storage providers to MASV.
## Reference
[Section titled “Reference”](#reference)
* **[CLI Reference](/agent/cli-reference/)** — Complete list of all Agent commands and parameters.
* **[Settings](/agent/settings/)** — Configure disk, FIFO mode, multiconnect, and rate-limit options.
# Authentication
> Authenticate MASV Agent using API keys or user credentials to enable session-dependent operations like Team uploads and automations.
Some actions require a valid user session. For example, MASV Agent requires a valid user session when sending a Package to an email recipient or creating download automations. Other actions, like uploading to a Portal, do not require a user session.
When you try to perform an action without a valid session, the service responds with a `401 Unauthorized` status code:
```json
{
"code": 262146,
"message": "session is nil"
}
```
## Authenticate with an API key
[Section titled “Authenticate with an API key”](#authenticate-with-an-api-key)
Authenticate with an [API key](/api/api-keys/) for persistent, session-free access. After you have generated your API key, pass it as a flag when you start the server:
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
export MASV_API_KEY="your-key-here"
masv server start --api-key="$MASV_API_KEY"
```
## Authenticate with credentials
[Section titled “Authenticate with credentials”](#authenticate-with-credentials)
Use the following command to log in with your email and password.
* CLI
```bash
masv user login --email $EMAIL --password "$MASV_PASSWORD"
```
* REST API
```bash
curl -X POST -H "Content-Type: application/json" \
http://localhost:8080/api/v1/login \
-d '{"email":"$EMAIL","password":"$PASSWORD"}'
```
## Check the current session
[Section titled “Check the current session”](#check-the-current-session)
To see which user currently has a valid session with MASV Agent:
* CLI
```bash
masv user status
```
* REST API
```bash
curl -X GET http://localhost:8080/api/v1/login
```
The response includes the logged-in user and their Teams:
```json
{
"user": {
"email": "me@domain.tld",
"id": "01CWEEY5PT3535ETT4TH9NYZVP",
"name": "Firstname Lastname"
},
"teams": [
{
"custom_expiry_default": 30,
"custom_expiry_enabled": true,
"download_limit_default": 12,
"download_limit_enabled": true,
"id": "01CWEEY60MREFF7PPYZ82QSQ9J",
"max_email_recipients": 30,
"name": "Acme",
"subdomain": "acme"
}
]
}
```
* `user` — the logged-in user’s attributes.
* `teams` — an array of all Teams the user belongs to, with limits and settings configured for each Team.
## List Teams
[Section titled “List Teams”](#list-teams)
Retrieve the Teams that the current user belongs to:
* CLI
```bash
masv user teams
```
* REST API
```bash
curl -X GET http://localhost:8080/api/v1/teams
```
## Handle authentication errors
[Section titled “Handle authentication errors”](#handle-authentication-errors)
If there is no user session, or authentication failed, MASV Agent responds with a `401 Unauthorized` status:
```http
HTTP/1.1 401 Unauthorized
Content-Type: application/json
```
## Next steps
[Section titled “Next steps”](#next-steps)
* **[Uploads](/agent/uploads/)** — Send files via Team email, shareable Link, or Portal.
* **[Downloads](/agent/downloads/)** — Initiate, monitor, and manage file downloads.
* **[Automations](/agent/automations/)** — Set up automated download and upload workflows.
# Automations
> Automate file transfers with MASV Agent using Portal download, all-Portals download, watch folder, and stream upload automations.
MASV Agent lets you automate file transfer tasks to save time and support robust workflows. Creating an automation returns an `automation_id`, which you can use to query, update, and delete the automation.
## List all automations
[Section titled “List all automations”](#list-all-automations)
* CLI
```bash
masv automation ls
```
* REST API
```bash
curl -X GET http://localhost:8080/api/v1/automations
```
***
## Portal download
[Section titled “Portal download”](#portal-download)
A Portal download automation automatically picks up new Packages uploaded to a specified Portal and queues them as downloads. The downloads can be managed the same way as user-initiated downloads.
### Create a Portal download automation
[Section titled “Create a Portal download automation”](#create-a-portal-download-automation)
* CLI
```bash
masv automation add download \
--name "AUTOMATION_NAME" \
--subdomain "PORTAL_SUBDOMAIN" \
--destination "DESTINATION_FOLDER" \
--create-package-folder=true \
--effective-time "2025-01-20 00:00:00" \
--enable=true \
--priority 1
```
| Name | Type | Required | Description |
| ----------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `name` | String | Yes | Custom automation name. |
| `subdomain` | String | Yes | Subdomain of the Portal to monitor. The Portal must be owned by the Team of the signed-in user. |
| `destination` | String | Yes | Destination folder where Packages will be downloaded. |
| `create-package-folder` | Boolean | No | Place Package contents in dedicated subdirectories (default true). |
| `effective-time` | String | No | RFC 3339 date/time after which uploaded Packages will be downloaded. Setting a past date downloads all Packages uploaded after that point. |
| `enable` | Boolean | No | Enable the automation immediately (default true). |
| `priority` | Integer | No | Transfer priority assigned to all created downloads. |
* REST API
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/automations/portal_download -d '{
"name": "AUTOMATION_NAME",
"portal_subdomain": "PORTAL_SUBDOMAIN",
"dst_folder": "DESTINATION_FOLDER",
"create_package_folder": true,
"effective_time": "2010-12-12T23:59:59-05:00",
"enabled": true,
"download_priority": 1
}'
```
| Name | Type | Required | Description |
| ----------------------- | ------- | -------- | ----------------------------------------------------------------------------------------------- |
| `name` | String | Yes | Custom automation name. |
| `portal_subdomain` | String | Yes | Subdomain of the Portal to monitor. The Portal must be owned by the Team of the signed-in user. |
| `dst_folder` | String | Yes | Destination folder where Packages will be downloaded. |
| `create_package_folder` | Boolean | No | Place Package contents in dedicated subdirectories. |
| `effective_time` | String | No | RFC 3339 date/time after which uploaded Packages will be downloaded. |
| `enabled` | Boolean | No | Enable the automation immediately. |
| `download_priority` | Integer | No | Transfer priority assigned to all created downloads. |
### Update a Portal download automation
[Section titled “Update a Portal download automation”](#update-a-portal-download-automation)
* CLI
```bash
masv automation update download $AUTOMATION_ID \
--name "NEW_AUTOMATION_NAME" --disable
```
Updatable fields: `create-package-folder`, `destination`, `priority`, `effective-time`, `enabled`, `name`, `subdomain`.
* REST API
```bash
curl -H "Content-Type: application/json" -X PUT \
http://localhost:8080/api/v1/automations/portal_download/{automation_id} -d '{
"name": "NEW_AUTOMATION_NAME",
"enabled": false
}'
```
Updatable fields: `create_package_folder`, `dst_folder`, `download_priority`, `effective_time`, `enabled`, `name`, `portal_subdomain`.
***
## All Portals download
[Section titled “All Portals download”](#all-portals-download)
An all-Portals download automation polls every Portal associated with a specified Team for available Packages and queues them as downloads.
### Create an all-Portals download automation
[Section titled “Create an all-Portals download automation”](#create-an-all-portals-download-automation)
* CLI
```bash
masv automation add download \
--all \
--name "AUTOMATION_NAME" \
--team-id "TEAM_ID" \
--create-package-folder=true \
--create-portal-subfolder=true \
--destination "DESTINATION" \
--effective-time "2025-01-20 00:00:00" \
--enable=true \
--priority 8
```
| Name | Type | Required | Description |
| ------------------------- | ------- | -------- | --------------------------------------------------------------------------------- |
| `all` | Flag | Yes | Indicates this is an all-Portals download automation. |
| `name` | String | Yes | Custom automation name. |
| `team-id` | String | Yes | Team ID to poll for Portal Packages. The signed-in user must belong to this Team. |
| `destination` | String | Yes | Destination folder where Packages will be downloaded. |
| `create-package-folder` | Boolean | No | Place Package contents in dedicated subdirectories (default true). |
| `create-portal-subfolder` | Boolean | No | Place Package folders within directories named after the originating Portal. |
| `effective-time` | String | No | RFC 3339 date/time after which uploaded Packages will be downloaded. |
| `enable` | Boolean | No | Enable the automation immediately (default true). |
| `priority` | Integer | No | Transfer priority assigned to all created downloads. |
* REST API
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/automations/all_portals_download -d '{
"name": "AUTOMATION_NAME",
"team_id": "TEAM_ID",
"dst_folder": "DESTINATION_FOLDER",
"create_portal_subfolder": true,
"effective_time": "2010-12-12T23:59:59-05:00",
"enabled": true,
"download_priority": 3
}'
```
| Name | Type | Required | Description |
| ------------------------- | ------- | -------- | --------------------------------------------------------------------------------- |
| `name` | String | Yes | Custom automation name. |
| `team_id` | String | Yes | Team ID to poll for Portal Packages. The signed-in user must belong to this Team. |
| `dst_folder` | String | Yes | Destination folder where Packages will be downloaded. |
| `create_portal_subfolder` | Boolean | No | Place Package folders within directories named after the originating Portal. |
| `effective_time` | String | No | RFC 3339 date/time after which uploaded Packages will be downloaded. |
| `enabled` | Boolean | No | Enable the automation immediately. |
| `download_priority` | Integer | No | Transfer priority assigned to all created downloads. |
### Update an all-Portals download automation
[Section titled “Update an all-Portals download automation”](#update-an-all-portals-download-automation)
* CLI
```bash
masv automation update download $AUTOMATION_ID \
--name "NEW_AUTOMATION_NAME" --disable
```
Updatable fields: `create-portal-subfolder`, `destination`, `priority`, `effective-time`, `enabled`, `name`, `team-id`.
* REST API
```bash
curl -H "Content-Type: application/json" -X PUT \
http://localhost:8080/api/v1/automations/all_portals_download/{automation_id} -d '{
"name": "NEW_AUTOMATION_NAME",
"enabled": false
}'
```
Updatable fields: `create_portal_subfolder`, `dst_folder`, `download_priority`, `effective_time`, `enabled`, `name`, `team_id`.
***
## Watch folder
[Section titled “Watch folder”](#watch-folder)
A watch folder is an upload automation that monitors a directory for subfolders. After a timeout passes without detecting file modifications, each subfolder is sent as a Package.
Caution
Top-level files in the watch folder are ignored by default. Place the files you want delivered inside subfolders. Use the `upload-loose-files` option to also send top-level files as individual Packages.
There are two types of watch folder automations:
* **Send (Team) upload** — sends subfolders to configured email recipients.
* **Portal upload** — sends subfolders to a configured Portal.
Note
MASV Agent cannot upload to a Portal that requires [custom metadata](/api/custom-metadata/).
### Send (Team) watch folder
[Section titled “Send (Team) watch folder”](#send-team-watch-folder)
#### Create
[Section titled “Create”](#create)
* CLI
```bash
masv automation add upload email \
--name "AUTOMATION_NAME" \
--enable=true \
--path "PATH_TO_WATCH" \
--recipients 'example1@test.com,example2@test.com' \
--team-id "TEAM_ID" \
--timeout 5
```
**Required fields:**
| Name | Type | Description |
| ------------ | ------- | ---------------------------------------------------------------------- |
| `name` | String | Automation name for identification. |
| `enable` | Boolean | Enabled/disabled state of the automation. |
| `path` | String | Directory to watch. Subfolders are sent as Packages after the timeout. |
| `recipients` | String | Comma-separated list of email recipients. |
| `team-id` | String | Team ID from which to send the Packages. |
| `timeout` | Integer | Filesystem inactivity timeout in minutes. |
**Optional fields:** `blacklist`, `delete-after`, `delete-files-after-upload`, `download-limit`, `download-password`, `message`, `package-name-suffix`, `priority`, `tag-id`, `tag-name`, `unlimited-storage`, `upload-loose-files`.
* REST API
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/automations/team_upload -d '{
"name": "send automation 1",
"path": "/path/to/watch/folder",
"timeout": 5,
"enabled": true,
"team_id": "TEAM_ID",
"recipient_emails": ["recipient@email.com"]
}'
```
**Required fields:**
| Name | Type | Description |
| ------------------ | ------- | ---------------------------------------------------------------------- |
| `name` | String | Automation name for identification. |
| `enabled` | Boolean | Enabled/disabled state of the automation. |
| `path` | String | Directory to watch. Subfolders are sent as Packages after the timeout. |
| `recipient_emails` | Array | Array of email recipients. |
| `team_id` | String | Team ID from which to send the Packages. |
| `timeout` | Integer | Filesystem inactivity timeout in minutes. |
**Optional fields:** `blacklist`, `delete_after`, `delete_files_after_upload`, `download_limit`, `download_password`, `message`, `package_name_suffix`, `send_loose_files`, `tag`, `unlimited_storage`, `upload_priority`.
#### Update
[Section titled “Update”](#update)
* CLI
```bash
masv automation update upload email $AUTOMATION_ID \
--name "NEW_AUTOMATION_NAME" --disable
```
* REST API
```bash
curl -H "Content-Type: application/json" -X PUT \
http://localhost:8080/api/v1/automations/team_upload/{automation_id} -d '{
"name": "name2"
}'
```
### Portal watch folder
[Section titled “Portal watch folder”](#portal-watch-folder)
#### Create
[Section titled “Create”](#create-1)
* CLI
```bash
masv automation add upload portal \
--enable=true \
--name "AUTOMATION_NAME" \
--path "PATH_TO_WATCH" \
--subdomain "PORTAL_SUBDOMAIN" \
--sender "test@example.com" \
--timeout 5
```
**Required fields:**
| Name | Type | Description |
| ----------- | ------- | ---------------------------------------------------------------------- |
| `enable` | Boolean | Enabled/disabled state of the automation. |
| `name` | String | Automation name for identification. |
| `path` | String | Directory to watch. Subfolders are sent as Packages after the timeout. |
| `subdomain` | String | Portal subdomain to send Packages to. |
| `sender` | String | Sender email that the recipient will see. |
| `timeout` | Integer | Filesystem inactivity timeout in minutes. |
**Optional fields:** `blacklist`, `delete-files-after-upload`, `message`, `package-name-suffix`, `password`, `priority`, `upload-loose-files`.
* REST API
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/automations/portal_upload -d '{
"name": "portal upload automation 1",
"path": "/path/to/watch/folder",
"timeout": 5,
"enabled": true,
"portal_subdomain": "PORTAL_SUBDOMAIN",
"sender_email": "sender@email.com"
}'
```
**Required fields:**
| Name | Type | Description |
| ------------------ | ------- | ---------------------------------------------------------------------- |
| `enabled` | Boolean | Enabled/disabled state of the automation. |
| `name` | String | Automation name for identification. |
| `path` | String | Directory to watch. Subfolders are sent as Packages after the timeout. |
| `portal_subdomain` | String | Portal subdomain to send Packages to. |
| `sender_email` | String | Sender email that the recipient will see. |
| `timeout` | Integer | Filesystem inactivity timeout in minutes. |
**Optional fields:** `blacklist`, `delete_files_after_upload`, `message`, `package_name_suffix`, `portal_password`, `send_loose_files`, `upload_priority`.
#### Update
[Section titled “Update”](#update-1)
* CLI
```bash
masv automation update upload portal $AUTOMATION_ID \
--name "NEW_AUTOMATION_NAME" --disable
```
* REST API
```bash
curl -H "Content-Type: application/json" -X PUT \
http://localhost:8080/api/v1/automations/portal_upload/{automation_id} -d '{
"name": "name2"
}'
```
***
## Stream upload
[Section titled “Stream upload”](#stream-upload)
A stream upload automation uploads growing files to recipients. Each stream automation watches a folder for new files. After a timeout passes without detecting file size changes, the growing file upload is finalized.
Caution
Stream upload automations only work with file formats and codecs that append to the end of a file. Modifications to already-streamed chunks will not be synced with the resulting Package. Directories within the stream folder are ignored — place growing files directly in the stream folder.
There are two types of stream upload automations:
* **Send (Team) stream** — sends growing files to configured email recipients.
* **Portal stream** — sends growing files to a configured Portal.
Note
MASV Agent cannot stream to a Portal that requires [custom metadata](/api/custom-metadata/).
### Send (Team) stream
[Section titled “Send (Team) stream”](#send-team-stream)
#### Create
[Section titled “Create”](#create-2)
* CLI
```bash
masv automation add upload email \
--name "AUTOMATION_NAME" \
--enable=true \
--path "PATH_TO_WATCH" \
--recipients 'example1@test.com,example2@test.com' \
--team-id "TEAM_ID" \
--timeout 5 \
--growing-files
```
**Required fields:**
| Name | Type | Description |
| ------------ | ------- | ----------------------------------------------------------------------------------------------- |
| `name` | String | Automation name for identification. |
| `enable` | Boolean | Enabled/disabled state of the automation. |
| `path` | String | Directory to watch. Uploads are created when new top-level files are detected. |
| `recipients` | String | Comma-separated list of email recipients. |
| `team-id` | String | Team ID from which to send the Packages. |
| `timeout` | Integer | Filesystem inactivity timeout in seconds. After this duration, any ongoing stream is finalized. |
**Optional fields:** `whitelist`, `delete-after`, `download-limit`, `download-password`, `message`, `priority`, `tag-id`, `tag-name`, `unlimited-storage`.
* REST API
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/automations/team_stream -d '{
"name": "team stream automation 1",
"team_id": "TEAM_ID",
"path": "/path/to/stream/folder",
"timeout": 60,
"recipient_emails": ["recipient@email.com"],
"enabled": true
}'
```
**Required fields:**
| Name | Type | Description |
| ------------------ | ------- | ----------------------------------------------------------------------------------------------- |
| `name` | String | Automation name for identification. |
| `enabled` | Boolean | Enabled/disabled state of the automation. |
| `path` | String | Directory to watch. Uploads are created when new top-level files are detected. |
| `recipient_emails` | Array | Array of email recipients. |
| `team_id` | String | Team ID from which to send the Packages. |
| `timeout` | Integer | Filesystem inactivity timeout in seconds. After this duration, any ongoing stream is finalized. |
**Optional fields:** `whitelist`, `delete_after`, `download_limit`, `download_password`, `message`, `tag`, `unlimited_storage`, `upload_priority`.
#### Update
[Section titled “Update”](#update-2)
* CLI
```bash
masv automation update upload team $AUTOMATION_ID \
--name "NEW_AUTOMATION_NAME" --disable
```
* REST API
```bash
curl -H "Content-Type: application/json" -X PUT \
http://localhost:8080/api/v1/automations/team_stream/{automation_id} -d '{
"name": "team stream automation 2"
}'
```
### Portal stream
[Section titled “Portal stream”](#portal-stream)
#### Create
[Section titled “Create”](#create-3)
* CLI
```bash
masv automation add upload portal \
--enable=true \
--name "AUTOMATION NAME" \
--path "PATH_TO_WATCH" \
--subdomain "SUBDOMAIN" \
--sender "test@example.com" \
--timeout 5 \
--growing-files
```
**Required fields:**
| Name | Type | Description |
| ----------- | ------- | ----------------------------------------------------------------------------------------------- |
| `enable` | Boolean | Enabled/disabled state of the automation. |
| `name` | String | Automation name for identification. |
| `path` | String | Directory to watch. Uploads are created when new top-level files are detected. |
| `subdomain` | String | Portal subdomain to send Packages to. |
| `sender` | String | Sender email that the recipient will see. |
| `timeout` | Integer | Filesystem inactivity timeout in seconds. After this duration, the ongoing stream is finalized. |
**Optional fields:** `whitelist`, `message`, `password`, `priority`.
* REST API
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/automations/portal_stream -d '{
"name": "portal stream automation 1",
"path": "/path/to/stream/folder",
"timeout": 60,
"enabled": true,
"portal_subdomain": "PORTAL_SUBDOMAIN",
"sender_email": "sender@email.com"
}'
```
**Required fields:**
| Name | Type | Description |
| ------------------ | ------- | ----------------------------------------------------------------------------------------------- |
| `enabled` | Boolean | Enabled/disabled state of the automation. |
| `name` | String | Automation name for identification. |
| `path` | String | Directory to watch. Uploads are created when new top-level files are detected. |
| `portal_subdomain` | String | Portal subdomain to send Packages to. |
| `sender_email` | String | Sender email that the recipient will see. |
| `timeout` | Integer | Filesystem inactivity timeout in seconds. After this duration, the ongoing stream is finalized. |
**Optional fields:** `whitelist`, `message`, `portal_password`, `upload_priority`.
#### Update
[Section titled “Update”](#update-3)
* CLI
```bash
masv automation update upload portal $AUTOMATION_ID \
--name "NEW_AUTOMATION_NAME" --disable
```
* REST API
```bash
curl -H "Content-Type: application/json" -X PUT \
http://localhost:8080/api/v1/automations/portal_stream/{automation_id} -d '{
"name": "name2"
}'
```
***
## Remove an automation
[Section titled “Remove an automation”](#remove-an-automation)
* CLI
Delete any automation type:
```bash
masv automation rm $AUTOMATION_ID
```
* REST API
Use the endpoint matching the automation type:
```bash
curl -X DELETE http://localhost:8080/api/v1/automations/portal_download/{automation_id}
curl -X DELETE http://localhost:8080/api/v1/automations/all_portals_download/{automation_id}
curl -X DELETE http://localhost:8080/api/v1/automations/team_upload/{automation_id}
curl -X DELETE http://localhost:8080/api/v1/automations/portal_upload/{automation_id}
curl -X DELETE http://localhost:8080/api/v1/automations/team_stream/{automation_id}
curl -X DELETE http://localhost:8080/api/v1/automations/portal_stream/{automation_id}
```
***
## Export and import automations
[Section titled “Export and import automations”](#export-and-import-automations)
Automations can be exported to a base64 string and imported by another MASV Agent or Desktop App. Use this to share or duplicate automations.
### Export
[Section titled “Export”](#export)
* CLI
```bash
masv automation export $AUTOMATION_ID
```
* REST API
Use the endpoint matching the automation type:
```bash
curl http://localhost:8080/api/v1/automations/portal_download/{automation_id}/export
curl http://localhost:8080/api/v1/automations/portal_upload/{automation_id}/export
curl http://localhost:8080/api/v1/automations/team_upload/{automation_id}/export
curl http://localhost:8080/api/v1/automations/team_stream/{automation_id}/export
curl http://localhost:8080/api/v1/automations/portal_stream/{automation_id}/export
```
The response contains a `data` field with the base64-encoded automation:
```json
{ "data": "base64_data" }
```
### Import
[Section titled “Import”](#import)
* CLI
```bash
masv automation import 'base64_data'
```
* REST API
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/automations/import -d '{
"data": "base64_data"
}'
```
***
## Next steps
[Section titled “Next steps”](#next-steps)
* **[Uploads](/agent/uploads/)** — Send files manually via Team email, shareable Link, or Portal.
* **[Downloads](/agent/downloads/)** — Initiate, monitor, and manage file downloads.
* **[Storage Gateway](/agent/storage-gateway/)** — Connect storage devices to MASV.
# CLI Reference
> Complete reference of all MASV Agent CLI commands and parameters for server, user, upload, download, automation, gateway, and settings.
This page lists every MASV Agent CLI command and its parameters. For detailed usage examples, see the linked guide pages.
## Server
[Section titled “Server”](#server)
Manage the MASV Agent local server. See [Getting Started](/agent/getting-started/) for details.
| Command | Description |
| ---------------------- | ---------------------------------------------------- |
| `masv server start` | Start the local MASV Agent server. |
| `masv server shutdown` | Gracefully stop the local server. |
| `masv version` | Display the installed version and available updates. |
| `masv -h` | List all available commands and flags. |
### `masv server start` parameters
[Section titled “masv server start parameters”](#masv-server-start-parameters)
| Flag | Type | Default | Description |
| ------------------------- | ------- | ----------------------- | --------------------------------------------------------------------------- |
| `--api-key` | String | — | MASV API key for authentication. |
| `--auto-finalize` | Boolean | `true` | Automatically finalize uploads when they reach 100% progress. |
| `--auto-resume` | Boolean | `true` | Automatically resume transfers when the server starts. |
| `--chunk-size` | String | `100MB` | Target chunk size for uploads and downloads, formatted as `{number}{unit}`. |
| `--config-dir` | String | `$HOME/.masvsrv` | Storage location for configuration files and the database. |
| `--listen` | String | `http://localhost:8080` | Listen address and port (port 0 for system-assigned). |
| `--log-format` | String | `[%lvl%] %time%: %msg%` | Log format string. |
| `--log-level` | String | `debug` | Log level: `debug`, `info`, `warning`, `error`. |
| `--log-out` | String | `stdout` | Log output destination: `stdout`, `stderr`, or a filename. |
| `--log-timestamp-format` | String | `15:04:05` | Timestamp format for log entries. |
| `--memory-cache` | Boolean | `true` | Use RAM to boost transfer performance. |
| `--transfer-cleanup-days` | Integer | — | Automatically delete transfers older than this many days (minimum 1). |
| `--version` | — | — | Display the current version and available updates. |
Tip
Store your API key in an environment variable and pass it as `--api-key="$MASV_API_KEY"`. This prevents the key from appearing in shell history and process listings.
***
## User
[Section titled “User”](#user)
Manage user sessions. See [Authentication](/agent/authentication/) for details.
| Command | Description |
| ------------------ | ------------------------------------------------------ |
| `masv user login` | Log in with email and password. |
| `masv user status` | Show the currently authenticated user and their Teams. |
| `masv user teams` | List Teams the current user belongs to. |
### `masv user login` parameters
[Section titled “masv user login parameters”](#masv-user-login-parameters)
| Flag | Type | Required | Description |
| ------------ | ------ | -------- | ------------------- |
| `--email` | String | Yes | User email address. |
| `--password` | String | Yes | User password. |
Caution
Passing passwords as command-line arguments exposes them in process listings and shell history. If the CLI supports interactive password prompts, prefer that method.
***
## Upload
[Section titled “Upload”](#upload)
Create, monitor, and manage uploads. See [Uploads](/agent/uploads/) for details.
| Command | Description |
| ---------------------------------- | ----------------------------------------------------------- |
| `masv upload start email` | Send a Package to email recipients. |
| `masv upload start link` | Send a Package as a shareable download Link. |
| `masv upload start portal` | Upload a Package to a Portal. |
| `masv upload link {upload_id}` | Create an additional shareable Link for an existing upload. |
| `masv upload finalize {upload_id}` | Finalize an upload and notify recipients. |
| `masv upload ls` | List all uploads. |
| `masv upload status {upload_id}` | View details for a specific upload. |
| `masv upload pause {upload_id}` | Pause a specific upload. |
| `masv upload pause all` | Pause all uploads. |
| `masv upload resume {upload_id}` | Resume a specific upload. |
| `masv upload resume all` | Resume all uploads. |
| `masv upload rm {upload_id}` | Delete a specific upload. |
| `masv upload rm all` | Delete all uploads. |
| `masv upload update {upload_id}` | Update upload properties (for example, FIFO rank). |
### Common upload parameters
[Section titled “Common upload parameters”](#common-upload-parameters)
These parameters apply to all upload types (`email`, `link`, `portal`).
| Flag | Type | Required | Default | Description |
| --------------- | ------- | -------- | ------- | -------------------------------------------------------------- |
| `--chunk-size` | String | No | `""` | Custom chunk size. Overrides the server-level chunk-size flag. |
| `--description` | String | No | `""` | Package description. |
| `--name` | String | No | — | Package name. |
| `--priority` | Integer | No | 0 | Upload priority. Higher values mean higher priority. |
### `masv upload start email` parameters
[Section titled “masv upload start email parameters”](#masv-upload-start-email-parameters)
Includes all common upload parameters plus:
| Flag | Type | Required | Description |
| --------------------- | ------- | -------- | ----------------------------------------------------- |
| `--emails` | String | Yes | Comma-separated list of email recipients. |
| `--team-id` | String | Yes | Team ID to associate with this upload. |
| `--password` | String | No | Default password for download Links. |
| `--delete-after` | Integer | No | Days to keep the Package in MASV storage. |
| `--download-limit` | Integer | No | Default access limit for download Links. |
| `--tag-name` | String | No | Name of the Package tag. Created if it doesn’t exist. |
| `--tag-id` | String | No | ID of an existing Package tag. |
| `--teamspace-id` | String | No | Teamspace ID to associate with this upload. |
| `--unlimited-storage` | Boolean | No | Enable unlimited storage for the Package. |
### `masv upload start link` parameters
[Section titled “masv upload start link parameters”](#masv-upload-start-link-parameters)
Includes all common upload parameters plus:
| Flag | Type | Required | Description |
| --------------------- | ------- | -------- | ----------------------------------------------------- |
| `--team-id` | String | Yes | Team ID to associate with this upload. |
| `--password` | String | No | Default password for download Links. |
| `--delete-after` | Integer | No | Days to keep the Package in MASV storage. |
| `--download-limit` | Integer | No | Default access limit for download Links. |
| `--tag-name` | String | No | Name of the Package tag. Created if it doesn’t exist. |
| `--tag-id` | String | No | ID of an existing Package tag. |
| `--teamspace-id` | String | No | Teamspace ID to associate with this upload. |
| `--unlimited-storage` | Boolean | No | Enable unlimited storage for the Package. |
### `masv upload start portal` parameters
[Section titled “masv upload start portal parameters”](#masv-upload-start-portal-parameters)
Includes all common upload parameters plus:
| Flag | Type | Required | Description |
| ------------- | ------ | -------- | --------------------------------------------------------------- |
| `--subdomain` | String | Yes | Target Portal subdomain. |
| `--sender` | String | Yes | Sender email address. |
| `--password` | String | No | Portal access code; required for password-protected Portals. |
| `--metadata` | String | No | Key-value pairs for Portal metadata: `key1=value1,key2=value2`. |
### `masv upload update` parameters
[Section titled “masv upload update parameters”](#masv-upload-update-parameters)
| Flag | Type | Required | Description |
| ------------- | ------- | -------- | ---------------------------------- |
| `--fifo-rank` | Integer | No | Set the FIFO rank for this upload. |
***
## Download
[Section titled “Download”](#download)
Initiate, monitor, and manage downloads. See [Downloads](/agent/downloads/) for details.
| Command | Description |
| ----------------------------------------------- | ---------------------------------------------------- |
| `masv download start` | Download a Package from a Link URL. |
| `masv download ls` | List all downloads. |
| `masv download status {download_id}` | View details for a specific download. |
| `masv download pause {download_id}` | Pause a specific download. |
| `masv download pause all` | Pause all downloads. |
| `masv download resume {download_id}` | Resume a specific download. |
| `masv download resume all` | Resume all downloads. |
| `masv download rm {download_id}` | Delete a specific download. |
| `masv download rm {download_id} --remove-files` | Delete a download and its files from disk. |
| `masv download rm all` | Delete all downloads. |
| `masv download rm all --states=complete,error` | Delete downloads filtered by state. |
| `masv download update {download_id}` | Update download properties (for example, FIFO rank). |
### `masv download start` parameters
[Section titled “masv download start parameters”](#masv-download-start-parameters)
| Flag | Type | Required | Description |
| ----------------------------- | ------- | -------- | -------------------------------------------------------------------------- |
| `--url` | String | Yes | Full download Link URL. |
| `--destination` | String | Yes | Path to the download destination directory. |
| `--password` | String | No | Download password, if required by the Link. |
| `--bypass-package-quarantine` | Boolean | No | Ignore malware warnings and download unaffected files only (default true). |
| `--create-package-folder` | Boolean | No | Download files inside a Package folder (default true). |
| `--file-ids` | String | No | Comma-separated list of Package file IDs to include. |
| `--priority` | Integer | No | Download priority. Higher values mean higher priority. |
| `--gid` | Integer | No | GID override to assign file ownership. |
| `--uid` | Integer | No | UID override to assign file ownership. |
### `masv download rm` parameters
[Section titled “masv download rm parameters”](#masv-download-rm-parameters)
| Flag | Type | Required | Description |
| ---------------- | ------- | -------- | -------------------------------------------------- |
| `--remove-files` | Boolean | No | Also delete downloaded files from the file system. |
| `--states` | String | No | Comma-separated list of states to filter deletion. |
### `masv download update` parameters
[Section titled “masv download update parameters”](#masv-download-update-parameters)
| Flag | Type | Required | Description |
| ------------- | ------- | -------- | ------------------------------------ |
| `--fifo-rank` | Integer | No | Set the FIFO rank for this download. |
***
## Automation
[Section titled “Automation”](#automation)
Create, update, list, remove, export, and import automations. See [Automations](/agent/automations/) for details.
| Command | Description |
| ------------------------------------------- | --------------------------------------------------------- |
| `masv automation ls` | List all automations. |
| `masv automation add download` | Create a Portal download automation. |
| `masv automation add download --all` | Create an all-Portals download automation. |
| `masv automation add upload email` | Create a Team watch folder or stream upload automation. |
| `masv automation add upload portal` | Create a Portal watch folder or stream upload automation. |
| `masv automation update download {id}` | Update a download automation. |
| `masv automation update upload email {id}` | Update a Team upload automation. |
| `masv automation update upload team {id}` | Update a Team stream automation. |
| `masv automation update upload portal {id}` | Update a Portal upload or stream automation. |
| `masv automation rm {id}` | Delete an automation. |
| `masv automation export {id}` | Export an automation as a base64 string. |
| `masv automation import 'base64_data'` | Import an automation from a base64 string. |
### `masv automation add download` parameters (Portal download)
[Section titled “masv automation add download parameters (Portal download)”](#masv-automation-add-download-parameters-portal-download)
| Flag | Type | Required | Description |
| ------------------------- | ------- | -------- | ------------------------------------------------------------------ |
| `--name` | String | Yes | Custom automation name. |
| `--subdomain` | String | Yes | Portal subdomain to monitor. |
| `--destination` | String | Yes | Destination folder for downloaded Packages. |
| `--create-package-folder` | Boolean | No | Place Package contents in dedicated subdirectories (default true). |
| `--effective-time` | String | No | RFC 3339 date/time after which Packages will be downloaded. |
| `--enable` | Boolean | No | Enable the automation immediately (default true). |
| `--priority` | Integer | No | Transfer priority for created downloads. |
### `masv automation add download --all` parameters (all-Portals download)
[Section titled “masv automation add download --all parameters (all-Portals download)”](#masv-automation-add-download---all-parameters-all-portals-download)
Includes all Portal download parameters except `--subdomain`, plus:
| Flag | Type | Required | Description |
| --------------------------- | ------- | -------- | ------------------------------------------------------ |
| `--all` | Flag | Yes | Indicates this is an all-Portals download automation. |
| `--team-id` | String | Yes | Team ID to poll for Portal Packages. |
| `--create-portal-subfolder` | Boolean | No | Place Package folders within Portal-named directories. |
### `masv automation add upload email` parameters (Team watch folder)
[Section titled “masv automation add upload email parameters (Team watch folder)”](#masv-automation-add-upload-email-parameters-team-watch-folder)
| Flag | Type | Required | Description |
| ----------------------------- | ------- | -------- | ------------------------------------------------------------------------- |
| `--name` | String | Yes | Automation name. |
| `--enable` | Boolean | Yes | Enable or disable the automation. |
| `--path` | String | Yes | Directory to watch. |
| `--recipients` | String | Yes | Comma-separated list of email recipients. |
| `--team-id` | String | Yes | Team ID from which to send Packages. |
| `--timeout` | Integer | Yes | Filesystem inactivity timeout in minutes. |
| `--growing-files` | Flag | No | Enable stream upload mode (monitors growing files instead of subfolders). |
| `--blacklist` | String | No | File patterns to exclude. |
| `--delete-after` | Integer | No | Days to keep the Package in MASV storage. |
| `--delete-files-after-upload` | Boolean | No | Delete source files after upload completes. |
| `--download-limit` | Integer | No | Default access limit for download Links. |
| `--download-password` | String | No | Default password for download Links. |
| `--message` | String | No | Message to include with the Package. |
| `--package-name-suffix` | String | No | Suffix appended to the Package name. |
| `--priority` | Integer | No | Upload priority. |
| `--tag-id` | String | No | ID of an existing Package tag. |
| `--tag-name` | String | No | Name of the Package tag. |
| `--unlimited-storage` | Boolean | No | Enable unlimited storage for the Package. |
| `--upload-loose-files` | Boolean | No | Also send top-level files as individual Packages. |
### `masv automation add upload portal` parameters (Portal watch folder)
[Section titled “masv automation add upload portal parameters (Portal watch folder)”](#masv-automation-add-upload-portal-parameters-portal-watch-folder)
| Flag | Type | Required | Description |
| ----------------------------- | ------- | -------- | ------------------------------------------------------------------------- |
| `--name` | String | Yes | Automation name. |
| `--enable` | Boolean | Yes | Enable or disable the automation. |
| `--path` | String | Yes | Directory to watch. |
| `--subdomain` | String | Yes | Portal subdomain to send Packages to. |
| `--sender` | String | Yes | Sender email address. |
| `--timeout` | Integer | Yes | Filesystem inactivity timeout in minutes. |
| `--growing-files` | Flag | No | Enable stream upload mode (monitors growing files instead of subfolders). |
| `--blacklist` | String | No | File patterns to exclude. |
| `--delete-files-after-upload` | Boolean | No | Delete source files after upload completes. |
| `--message` | String | No | Message to include with the Package. |
| `--package-name-suffix` | String | No | Suffix appended to the Package name. |
| `--password` | String | No | Portal access code. |
| `--priority` | Integer | No | Upload priority. |
| `--upload-loose-files` | Boolean | No | Also send top-level files as individual Packages. |
***
## Gateway
[Section titled “Gateway”](#gateway)
Manage Storage Gateway connections. See [Storage Gateway](/agent/storage-gateway/) for details.
| Command | Description |
| ------------------------------------- | ------------------------------------------ |
| `masv gateway add` | Register a new Storage Gateway connection. |
| `masv gateway ls` | List all Storage Gateway connections. |
| `masv gateway rm {connection_id}` | Remove a Storage Gateway connection. |
| `masv gateway update {connection_id}` | Update a Storage Gateway connection. |
### `masv gateway add` parameters
[Section titled “masv gateway add parameters”](#masv-gateway-add-parameters)
| Flag | Type | Required | Description |
| --------------- | ------- | -------- | -------------------------------------------------------- |
| `--id` | String | Yes | ID of the existing Storage Device Integration. |
| `--secret` | String | Yes | Secret key to register the connection with the MASV API. |
| `--name` | String | Yes | Descriptive connection name. |
| `--root-path` | String | Yes | Folder to share. Must be an absolute path. |
| `--permissions` | String | Yes | File permissions: `r` (read) or `w` (write). |
| `--uid` | Integer | No | Custom user ID override for file ownership on Linux. |
| `--gid` | Integer | No | Custom group ID override for file ownership on Linux. |
Caution
Avoid passing secrets directly as command-line arguments. Use environment variables instead: `--secret "$GATEWAY_SECRET"`.
### `masv gateway update` parameters
[Section titled “masv gateway update parameters”](#masv-gateway-update-parameters)
| Flag | Type | Required | Description |
| ------------- | ------- | -------- | ---------------------------------------------------------- |
| `--root-path` | String | No | New root folder. Must be an absolute path. |
| `--uid` | Integer | No | Custom user ID override. Use `-1` to remove the override. |
| `--gid` | Integer | No | Custom group ID override. Use `-1` to remove the override. |
***
## Settings
[Section titled “Settings”](#settings)
Configure disk I/O, FIFO mode, multiconnect, network concurrency, and rate limits. See [Settings](/agent/settings/) for details.
| Command | Description |
| ----------------------------------------------------------- | --------------------------------------------------- |
| `masv settings disk status` | Query disk configurations. |
| `masv settings disk update` | Update disk configurations. |
| `masv settings fifo status` | Query FIFO mode status. |
| `masv settings fifo update` | Enable, disable, or configure FIFO mode. |
| `masv settings multiconnect status` | Query multiconnect status. |
| `masv settings multiconnect update` | Enable or disable multiconnect. |
| `masv settings multiconnect interface {iface_id}` | Enable or disable a network interface. |
| `masv settings network concurrency status` | Query network concurrency settings. |
| `masv settings network concurrency update` | Update network concurrency settings. |
| `masv settings network rate-limit upload status` | Query upload rate-limit settings. |
| `masv settings network rate-limit download status` | Query download rate-limit settings. |
| `masv settings network rate-limit download update` | Update download rate-limit settings. |
| `masv settings network rate-limit upload update` | Update upload rate-limit settings. |
| `masv settings network rate-limit download schedule update` | Enable or disable the download rate-limit schedule. |
| `masv settings network rate-limit download schedule days` | Configure schedule days and times. |
### `masv settings disk update` parameters
[Section titled “masv settings disk update parameters”](#masv-settings-disk-update-parameters)
| Flag | Type | Required | Description |
| ------------ | ------- | -------- | ---------------------------------------------------------------------------- |
| `--readers` | Integer | No | Maximum number of concurrent readers. |
| `--writers` | Integer | No | Maximum number of concurrent writers. |
| `--priority` | String | No | Disk priority: `mixed_read_writes`, `prioritize_writes`, `prioritize_reads`. |
| `--default` | Flag | No | Apply changes only to the default configuration. |
| `--ids` | String | No | Comma-separated disk IDs to update. |
### `masv settings fifo update` parameters
[Section titled “masv settings fifo update parameters”](#masv-settings-fifo-update-parameters)
| Flag | Type | Required | Description |
| ------------- | ------- | -------- | --------------------------------------- |
| `--enable` | Flag | No | Enable FIFO mode. |
| `--disable` | Flag | No | Disable FIFO mode. |
| `--downloads` | Integer | No | Number of concurrent downloads allowed. |
| `--uploads` | Integer | No | Number of concurrent uploads allowed. |
### `masv settings multiconnect update` parameters
[Section titled “masv settings multiconnect update parameters”](#masv-settings-multiconnect-update-parameters)
| Flag | Type | Required | Description |
| ----------- | ---- | -------- | --------------------- |
| `--enable` | Flag | No | Enable multiconnect. |
| `--disable` | Flag | No | Disable multiconnect. |
### `masv settings multiconnect interface` parameters
[Section titled “masv settings multiconnect interface parameters”](#masv-settings-multiconnect-interface-parameters)
| Flag | Type | Required | Description |
| ----------- | ---- | -------- | ------------------------------ |
| `--enable` | Flag | No | Enable the network interface. |
| `--disable` | Flag | No | Disable the network interface. |
### `masv settings network concurrency update` parameters
[Section titled “masv settings network concurrency update parameters”](#masv-settings-network-concurrency-update-parameters)
| Flag | Type | Required | Description |
| ------------ | ------- | -------- | --------------------------------- |
| `--download` | Integer | No | Number of download chunk workers. |
| `--upload` | Integer | No | Number of upload chunk workers. |
### `masv settings network rate-limit` update parameters
[Section titled “masv settings network rate-limit update parameters”](#masv-settings-network-rate-limit-update-parameters)
| Flag | Type | Required | Description |
| ------------ | ------- | -------- | ------------------------------------------------------------- |
| `--bps` | Integer | No | Rate limit in bits per second. |
| `--enable` | Flag | No | Enable the rate limit. |
| `--disable` | Flag | No | Disable the rate limit. |
| `--iface-id` | String | No | Network interface ID (required when multiconnect is enabled). |
### `masv settings network rate-limit schedule` parameters
[Section titled “masv settings network rate-limit schedule parameters”](#masv-settings-network-rate-limit-schedule-parameters)
| Flag | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------------------------------------------- |
| `--enable` | Flag | No | Enable the rate-limit schedule. |
| `--disable` | Flag | No | Disable the rate-limit schedule. |
| `--iface-id` | String | No | Network interface ID (required when multiconnect is enabled). |
### `masv settings network rate-limit schedule days` parameters
[Section titled “masv settings network rate-limit schedule days parameters”](#masv-settings-network-rate-limit-schedule-days-parameters)
| Flag | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------------------------------------------- |
| `--start` | String | Yes | Schedule start time (for example, `"09:00"`). |
| `--end` | String | Yes | Schedule end time (for example, `"17:00"`). |
| `--enable` | Flag | No | Enable the selected days. |
| `--disable` | Flag | No | Disable the selected days. |
| `--mon` | Flag | No | Include Monday. |
| `--tue` | Flag | No | Include Tuesday. |
| `--wed` | Flag | No | Include Wednesday. |
| `--thu` | Flag | No | Include Thursday. |
| `--fri` | Flag | No | Include Friday. |
| `--sat` | Flag | No | Include Saturday. |
| `--sun` | Flag | No | Include Sunday. |
| `--iface-id` | String | No | Network interface ID (required when multiconnect is enabled). |
# Download files with the MASV Agent
> Download MASV Packages with the Agent using CLI and REST API, including status monitoring and transfer management.
MASV Agent can download any MASV Package provided you have a valid download URL in the format `https://get.massive.io/{ID}?secret={SECRET}`. For password-protected downloads, you also need the password. MASV Agent rebuilds the directory structure of the Package as sent by the sender. Initiating and managing downloads does not require a user session.
Note
Special characters in file names for cross-platform downloads may result in modified file names. MASV Agent removes special characters to conform to the receiving platform’s file name requirements.
Downloads transition through these states:
| State | Description |
| -------------- | ----------------------------------------------------------------------------------- |
| `transferring` | The download is currently transferring data. |
| `paused` | The download is paused by the user. |
| `complete` | The download has been completed. |
| `error` | A fatal error was encountered. This may be recoverable depending on the error type. |
## Download a Package
[Section titled “Download a Package”](#download-a-package)
Supply the full download Link URL and a destination folder:
* CLI
```bash
masv download start \
--url "https://get.massive.io/$LINK_ID?secret=$LINK_SECRET" \
--destination /destination/folder
```
| Name | Type | Required | Description |
| --------------------------- | ------- | -------- | --------------------------------------------------------------------------------------------- |
| `bypass-package-quarantine` | Boolean | No | Ignore malware warnings and only download unaffected files (default true). |
| `create-package-folder` | Boolean | No | Download files inside a Package folder (default true). |
| `destination` | String | Yes | Path to the download destination directory. |
| `file-ids` | String | No | Comma-separated list of Package file IDs to include. Example: `'01FDFQW6Y9...,01HZMN7V90...'` |
| `password` | String | No | Download password that may be required by the Link. |
| `priority` | Integer | No | Download priority. Higher values mean higher priority. |
| `url` | String | Yes | The full download Link URL. |
| `gid` | Integer | No | GID override to assign file ownership. |
| `uid` | Integer | No | UID override to assign file ownership. |
* REST API
```bash
curl -X POST -H "Content-Type: application/json" \
http://localhost:8080/api/v1/downloads -d '{
"url": "https://get.massive.io/$LINK_ID?secret=$LINK_SECRET",
"dst_folder": "/destination/folder",
"password": "optional_password"
}'
```
| Name | Type | Required | Description |
| --------------------------- | ------- | -------- | ----------------------------------------------------------------------------------- |
| `bypass_package_quarantine` | Boolean | No | Ignore malware warnings and only download unaffected files (default true). |
| `chunk_size` | Integer | No | Target chunk size for the download. |
| `create_package_folder` | Boolean | No | Download files inside a Package folder (default true). |
| `dst_folder` | String | Yes | Path to the download destination directory. |
| `file_ids` | Array | No | Array of Package file IDs to include. Example: `["01FDFQW6Y9...", "01HZMN7V90..."]` |
| `password` | String | No | Download password that may be required by the Link. |
| `priority` | Integer | No | Download priority. Higher values mean higher priority. |
| `url` | String | Yes | The full download Link URL. |
| `group_id` | Integer | No | GID override to assign file ownership. |
| `user_id` | Integer | No | UID override to assign file ownership. |
If successful, MASV Agent responds with the new download ID:
```json
{
"download_id": "f98b1e92-1c67-48e4-9b9c-f2bb27a33375"
}
```
***
## View download status
[Section titled “View download status”](#view-download-status)
List all downloads managed by MASV Agent:
* CLI
```bash
masv download ls
```
* REST API
```bash
curl -X GET http://localhost:8080/api/v1/downloads
```
View full details for a specific download, including individual file states:
* CLI
```bash
masv download status $DOWNLOAD_ID
```
* REST API
```bash
curl -X GET http://localhost:8080/api/v1/downloads/{DOWNLOAD_ID}
```
***
## Manage downloads
[Section titled “Manage downloads”](#manage-downloads)
### Pause a download
[Section titled “Pause a download”](#pause-a-download)
* CLI
```bash
masv download pause $DOWNLOAD_ID
```
* REST API
```bash
curl -X POST http://localhost:8080/api/v1/downloads/{DOWNLOAD_ID}/pause
```
### Pause all downloads
[Section titled “Pause all downloads”](#pause-all-downloads)
* CLI
```bash
masv download pause all
```
* REST API
```bash
curl -X POST http://localhost:8080/api/v1/downloads/pause
```
### Resume a download
[Section titled “Resume a download”](#resume-a-download)
* CLI
```bash
masv download resume $DOWNLOAD_ID
```
* REST API
```bash
curl -X POST http://localhost:8080/api/v1/downloads/{DOWNLOAD_ID}/resume
```
### Resume all downloads
[Section titled “Resume all downloads”](#resume-all-downloads)
* CLI
```bash
masv download resume all
```
* REST API
```bash
curl -X POST http://localhost:8080/api/v1/downloads/resume
```
### Delete a download
[Section titled “Delete a download”](#delete-a-download)
* CLI
```bash
masv download rm $DOWNLOAD_ID
```
* REST API
```bash
curl -X DELETE http://localhost:8080/api/v1/downloads/{DOWNLOAD_ID}
```
Caution
Deleting a download does not automatically delete the completed files from the file system.
### Delete a download and its files
[Section titled “Delete a download and its files”](#delete-a-download-and-its-files)
* CLI
```bash
masv download rm $DOWNLOAD_ID --remove-files
```
* REST API
```bash
curl -X DELETE http://localhost:8080/api/v1/downloads/{DOWNLOAD_ID}?remove_files=true
```
### Delete all downloads
[Section titled “Delete all downloads”](#delete-all-downloads)
* CLI
```bash
masv download rm all
```
* REST API
```bash
curl -X DELETE http://localhost:8080/api/v1/downloads
```
### Delete downloads by state
[Section titled “Delete downloads by state”](#delete-downloads-by-state)
* CLI
```bash
masv download rm all --states=complete,error
```
* REST API
```bash
curl -X DELETE http://localhost:8080/api/v1/downloads?states=complete,error
```
***
## Next steps
[Section titled “Next steps”](#next-steps)
* **[Uploads](/agent/uploads/)** — Send files via Team email, shareable Link, or Portal.
* **[Automations](/agent/automations/)** — Set up automated download and upload workflows.
* **[Storage Gateway](/agent/storage-gateway/)** — Connect storage devices to MASV.
# Getting Started with MASV Agent
> Install and run MASV Agent on Linux, macOS, Windows, or Docker for automated headless file transfers.
MASV Agent is a cross-platform service that manages file transfers to and from the MASV network. It functions as a CLI tool backed by a local REST API server that you can also call directly.
The Agent supports simultaneous upload and download of multiple Packages, handles file system interactions, and is stateful — it stores transfer metadata and user sessions to survive interruptions and shutdowns. MASV Agent is used to transfer terabytes of data every day, reliably and quickly.
Before installing, check the [MASV Agent System Requirements](https://help.massive.io/en/masv-system-requirements#agent). For the latest changes, see the [MASV Agent Release Notes](https://help.massive.io/en/masv-agent-release-notes).
## Installation
[Section titled “Installation”](#installation)
Install MASV Agent natively on [Linux](/agent/install-linux/), [macOS](/agent/install-macos/), or [Windows](/agent/install-windows/). You can also run it as a [Docker container](/agent/install-docker/).
## Starting the server
[Section titled “Starting the server”](#starting-the-server)
To use MASV Agent, start the local server first:
```bash
masv server start
```
Common flags include `--api-key` for authentication, `--listen` to set the address and port, and `--config-dir` to change the storage location. For the full list of flags, see [CLI Reference](/agent/cli-reference/#masv-server-start-parameters).
Tip
Store your API key in an environment variable (`$MASV_API_KEY`) and reference it when starting the server. This prevents the key from appearing in shell history and process listings. See [Authentication](/agent/authentication/) for details.
After the server is running, you can use the rest of the MASV Agent commands.
## Basic commands
[Section titled “Basic commands”](#basic-commands)
List all available commands and flags:
```bash
masv -h
```
Check which version of MASV Agent is installed:
```bash
masv version
```
Gracefully stop the local server:
```bash
masv server shutdown
```
## Viewing logs
[Section titled “Viewing logs”](#viewing-logs)
The MASV Agent log helps you review activity and troubleshoot issues.
| Platform | How to view logs |
| -------- | ----------------------------------------------------------------------------------- |
| Docker | Run `docker container ls` to get the container ID, then `docker logs $CONTAINER_ID` |
| Linux | Run `journalctl -u masv-agent` to view systemd log output |
| macOS | Open `$HOME/masv-agent/main.log` |
| Windows | Open Event Viewer → Windows Logs → Application, and filter by source `masv-agent` |
## Next steps
[Section titled “Next steps”](#next-steps)
* **[Authentication](/agent/authentication/)** — Authenticate with API keys or credentials before transferring files.
* **[Uploads](/agent/uploads/)** — Send files via Team email, shareable Link, or Portal.
* **[Downloads](/agent/downloads/)** — Initiate, monitor, and manage file downloads.
* **[Automations](/agent/automations/)** — Set up watch folders, Portal downloads, and stream uploads.
* **[Storage Gateway](/agent/storage-gateway/)** — Connect cloud storage providers to MASV.
* **[CLI Reference](/agent/cli-reference/)** — Complete list of all Agent commands and parameters.
* **[Settings](/agent/settings/)** — Configure disk, FIFO mode, multiconnect, and rate-limit options.
# Install with Docker
> Run MASV Agent in a Docker container with volume configuration, startup options, and update procedures.
This guide covers setting up MASV Agent using Docker. For native installation on your operating system, see the platform-specific guides for [Linux](/agent/install-linux/), [macOS](/agent/install-macos/), or [Windows](/agent/install-windows/).
## Supported architectures
[Section titled “Supported architectures”](#supported-architectures)
MASV Agent supports multiple architectures for deployment in different environments. Use the `masvio/masv-agent:3.2.11` image tag to automatically pull the correct architecture for your system.
| Architecture | Tag |
| ------------ | -------- |
| `x86-64` | `3.2.11` |
| `arm64` | `3.2.11` |
Caution
Always pin to a specific version tag in production environments for reproducible deployments. Check [Docker Hub](https://hub.docker.com/r/masvio/masv-agent/tags) for the latest available version.
## Pulling the image
[Section titled “Pulling the image”](#pulling-the-image)
Ensure that [Docker](https://docs.docker.com/get-started/get-docker/) is installed on the host machine, then pull the MASV Agent image:
```bash
docker pull masvio/masv-agent:3.2.11
```
## Volume configuration
[Section titled “Volume configuration”](#volume-configuration)
MASV Agent stores configuration and transfer data in two directories. Use [Docker volumes](https://docs.docker.com/engine/storage/volumes/) to make these persistent across container restarts.
| Volume | Purpose |
| --------- | ------------------------------------------------------- |
| `/config` | Application configuration and saved transfer state |
| `/data` | Default mount point for automated downloads and uploads |
Create matching directories on the Docker host before starting the container:
```bash
mkdir ~/masv-agent
mkdir ~/masv-files
```
Tip
Create these directories before starting the container. Otherwise, Docker creates them with `root` as the owner.
## Container startup
[Section titled “Container startup”](#container-startup)
### Docker CLI
[Section titled “Docker CLI”](#docker-cli)
```bash
docker run -d \
--name masv-agent \
-u 1000:1000 \
-v ~/masv-agent:/config \
-v ~/masv-files:/data \
--restart unless-stopped \
masvio/masv-agent:3.2.11
```
* `-v ~/masv-agent:/config` maps the host directory to the container’s config directory. MASV Agent uses this to store configuration and transfer state. Keeping this data intact maintains transfer progress between restarts.
* `-v ~/masv-files:/data` maps the host directory to the container’s data directory, used for downloads and uploads.
* `-u 1000:1000` sets the user and group ID that MASV Agent runs as. Replace with the appropriate IDs for your environment if needed.
### Docker Compose
[Section titled “Docker Compose”](#docker-compose)
To authenticate with Docker Compose, define a [Docker environment variable](https://docs.docker.com/compose/how-tos/environment-variables/set-environment-variables/) or [Docker secret](https://docs.docker.com/engine/swarm/secrets/). This example uses an environment variable `API_KEY`:
```yaml
version: "3"
services:
masvagent:
image: masvio/masv-agent:3.2.11
container_name: masv-agent
user: "1000:1000"
# Store your API key in a .env file — never commit it to version control.
# See /api/api-keys/ for key management best practices.
environment:
- TZ=UTC
- API_KEY=${API_KEY}
volumes:
- ~/masv-agent:/config
- ~/masv-files:/data
ports:
- "127.0.0.1:8080:8080"
restart: unless-stopped
command: ["--api-key", "$API_KEY"]
```
Start the container:
```bash
docker-compose up
```
Caution
Never commit `.env` files to version control. Add `.env` to your `.gitignore` to prevent accidentally pushing secrets to your repository.
### Parameters
[Section titled “Parameters”](#parameters)
When launching MASV Agent with `masv server start`, you can configure it using command-line flags. See the full list in the [Getting Started guide](/agent/getting-started/#starting-the-server).
## Executing commands
[Section titled “Executing commands”](#executing-commands)
Prefix any `masv` command with `docker exec masv-agent` to run it inside the container:
```bash
docker exec masv-agent masv gateway ls
```
For convenience, set up a shell alias to streamline interactions:
```bash
alias masv='docker exec masv-agent masv'
masv gateway ls
```
Alternatively, open an interactive shell inside the container:
```bash
docker exec -i masv-agent bash
masv gateway ls
```
## Updating MASV Agent
[Section titled “Updating MASV Agent”](#updating-masv-agent)
Filesystem changes within a Docker container are not easily migrated to a new container. Store your configuration files in an external, persistent host directory (as described in [Volume configuration](#volume-configuration)) and follow these steps to update:
1. Stop the current MASV Agent container.
2. Back up the existing config directory (`~/masv-agent`) to a safe location. It contains configuration, transfer records, and any custom files such as scripts.
3. Pull the new MASV Agent image:
```bash
docker pull masvio/masv-agent:3.2.11
```
4. Point the new container to the existing config directory, or set up a new config directory and copy the old config files into it.
5. Start the new container.
6. Verify the new container is working as expected and records have been updated correctly.
7. Remove the old container.
Note
You can use the backed-up config directory to reinstall the previous version of MASV Agent if there are issues with an update. Delete the backup when you no longer need it.
## Next steps
[Section titled “Next steps”](#next-steps)
* **[Authentication](/agent/authentication/)** — Authenticate with API keys or credentials before transferring files.
* **[Uploads](/agent/uploads/)** — Send files via Team email, shareable Link, or Portal.
* **[Downloads](/agent/downloads/)** — Initiate, monitor, and manage file downloads.
* **[Storage Gateway](/agent/storage-gateway/)** — Connect cloud storage providers to MASV.
# Install on Linux
> Install and run MASV Agent natively on Linux using apt, yum, or dnf package managers.
This guide covers setting up MASV Agent on a Linux host machine. If you prefer to use Docker instead, see the [Docker setup guide](/agent/install-docker/).
## Installation
[Section titled “Installation”](#installation)
### Debian-based systems (apt)
[Section titled “Debian-based systems (apt)”](#debian-based-systems-apt)
```bash
curl -fsSL https://dl.massive.io/agent/publickey.asc | sudo gpg --dearmor -o /etc/apt/keyrings/masv-agent.gpg
echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/masv-agent.gpg] https://dl.massive.io/agent/deb stable main" | sudo tee /etc/apt/sources.list.d/masv-agent.list > /dev/null
sudo apt update
sudo apt install masv-agent
```
### RPM-based systems (yum)
[Section titled “RPM-based systems (yum)”](#rpm-based-systems-yum)
```bash
sudo tee -a /etc/yum.repos.d/masv-agent.repo > /dev/null < /dev/null < Install and run MASV Agent natively on macOS using Homebrew for automated file transfers.
This guide covers setting up MASV Agent on a macOS host machine. If you prefer to use Docker instead, see the [Docker setup guide](/agent/install-docker/).
## Installation
[Section titled “Installation”](#installation)
macOS agent installation is available via [Homebrew](https://brew.sh/):
```bash
brew tap masv/masv https://gitlab.com/masvio/opensource/homebrew-masv.git
brew install masv-agent
```
## Running MASV Agent
[Section titled “Running MASV Agent”](#running-masv-agent)
Package installation adds a `launchd` service file `io.masv.agent.plist` to `~/Library/LaunchAgents`. This service is configured to automatically start the MASV Agent background server on system startup using the command `masv server start`.
Manage the service with the following commands:
```bash
# Stop the running service
launchctl stop io.masv.agent
# Start a stopped service
launchctl start io.masv.agent
```
### Parameters
[Section titled “Parameters”](#parameters)
When launching MASV Agent with `masv server start`, you can configure it using command-line flags. See the full list in the [Getting Started guide](/agent/getting-started/#starting-the-server).
## Next steps
[Section titled “Next steps”](#next-steps)
* **[Authentication](/agent/authentication/)** — Authenticate with API keys or credentials before transferring files.
* **[Uploads](/agent/uploads/)** — Send files via Team email, shareable Link, or Portal.
* **[Downloads](/agent/downloads/)** — Initiate, monitor, and manage file downloads.
* **[Storage Gateway](/agent/storage-gateway/)** — Connect cloud storage providers to MASV.
# Install on Windows
> Install and run MASV Agent natively on Windows using Chocolatey for automated file transfers.
This guide covers setting up MASV Agent on a Windows host machine. If you prefer to use Docker instead, see the [Docker setup guide](/agent/install-docker/).
## Installation
[Section titled “Installation”](#installation)
Windows agent installation is available via [Chocolatey](https://chocolatey.org/). Run the following in an elevated PowerShell prompt:
```powershell
choco source add --name=MASV --source=https://dl.massive.io/agent/choco/index.json
choco install masv-agent
```
## Running MASV Agent
[Section titled “Running MASV Agent”](#running-masv-agent)
Package installation configures a Windows system service named `masv-agent`. This service is set to automatically start the MASV Agent background server after system startup using the command `masv server start`.
Note
The Windows background service is configured as **Automatic (Delayed Start)**. It can take a couple of minutes after system boot before the background service starts.
## Next steps
[Section titled “Next steps”](#next-steps)
* **[Authentication](/agent/authentication/)** — Authenticate with API keys or credentials before transferring files.
* **[Uploads](/agent/uploads/)** — Send files via Team email, shareable Link, or Portal.
* **[Downloads](/agent/downloads/)** — Initiate, monitor, and manage file downloads.
* **[Storage Gateway](/agent/storage-gateway/)** — Connect cloud storage providers to MASV.
# Settings
> Configure MASV Agent disk I/O, FIFO mode, multiconnect, network concurrency, and rate-limit settings via CLI and REST API.
Configure MASV Agent transfer behavior including disk I/O, bandwidth limits, FIFO queuing, multiconnect, and network concurrency.
***
## Disk configuration
[Section titled “Disk configuration”](#disk-configuration)
Configure how MASV Agent reads and writes data to each disk during transfers.
### Parameters
[Section titled “Parameters”](#parameters)
| Field | Type | Description |
| ------------------------ | ------- | ------------------------------------- |
| `disk_id` | String | Unique ID for the disk config. |
| `priority` | String | Disk read/write priority. |
| `max_concurrent_readers` | Integer | Maximum number of concurrent readers. |
| `max_concurrent_writers` | Integer | Maximum number of concurrent writers. |
### Priority values
[Section titled “Priority values”](#priority-values)
| Priority | Description |
| ------------------- | ------------------------------------------------------------------------------------- |
| `mixed_read_writes` | Allows concurrent read and write operations on the disk. |
| `prioritize_writes` | Delays all read operations while writing to disk. |
| `prioritize_reads` | Delays all write operations while reading from disk. Not recommended for typical use. |
### Query disk status
[Section titled “Query disk status”](#query-disk-status)
* CLI
```bash
masv settings disk status
```
* REST API
Query the default disk configuration:
```bash
curl http://localhost:8080/api/v1/settings/disk_config_default
```
Query all disk configurations:
```bash
curl http://localhost:8080/api/v1/settings/disk_status
```
### Update disk configuration
[Section titled “Update disk configuration”](#update-disk-configuration)
* CLI
Update both default and disk-specific configurations:
```bash
masv settings disk update --readers 2 --writers 3 --priority mixed_read_writes
```
Update only the default configuration:
```bash
masv settings disk update --readers 2 --default
```
Update specific disks by ID:
```bash
masv settings disk update --writers 2 --ids /,/mnt/hdd2
```
Create a new disk configuration by providing an ID that does not yet exist:
```bash
masv settings disk update --writers 1 --ids /mnt/hdd3
```
* REST API
Update the default disk configuration:
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/settings/disk_config_default -d '{
"priority": "mixed_read_writes",
"max_concurrent_readers": 8,
"max_concurrent_writers": 8
}'
```
Update or create disk configurations:
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/settings/disk_config -d '{
"/": {
"priority": "mixed_read_writes",
"max_concurrent_readers": 8,
"max_concurrent_writers": 8
},
"/mnt/hdd2": {
"priority": "mixed_read_writes",
"max_concurrent_readers": 8,
"max_concurrent_writers": 8
}
}'
```
***
## FIFO mode
[Section titled “FIFO mode”](#fifo-mode)
FIFO mode limits the allocated bandwidth to a fixed number of concurrent uploads and downloads. Transfer priority is granted in the order transfers are created, with the oldest transfers getting priority first.
### Query FIFO status
[Section titled “Query FIFO status”](#query-fifo-status)
* CLI
```bash
masv settings fifo status
```
* REST API
```bash
curl http://localhost:8080/api/v1/settings/fifo_mode
```
### Update FIFO mode
[Section titled “Update FIFO mode”](#update-fifo-mode)
* CLI
```bash
masv settings fifo update --enable --downloads 1 --uploads 2
masv settings fifo update --disable
```
* REST API
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/settings/fifo_mode -d '{
"active_download_count": 1,
"active_upload_count": 2,
"enabled": true
}'
```
### Modify FIFO ranks
[Section titled “Modify FIFO ranks”](#modify-fifo-ranks)
Alter the rank of existing transfers to change their priority in the FIFO queue:
* CLI
```bash
masv upload update $UPLOAD_ID --fifo-rank 1
masv download update $DOWNLOAD_ID --fifo-rank 1
```
* REST API
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/uploads/{upload_id}/fifo_rank -d '{"fifo_rank": 1}'
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/downloads/{download_id}/fifo_rank -d '{"fifo_rank": 1}'
```
***
## Multiconnect
[Section titled “Multiconnect”](#multiconnect)
Multiconnect allows MASV Agent to transfer data using multiple active internet connections available to the host system.
### Query multiconnect status
[Section titled “Query multiconnect status”](#query-multiconnect-status)
* CLI
```bash
masv settings multiconnect status
```
* REST API
```bash
curl http://localhost:8080/api/v1/settings/multiconnect/status
```
### Enable or disable multiconnect
[Section titled “Enable or disable multiconnect”](#enable-or-disable-multiconnect)
* CLI
```bash
masv settings multiconnect update --enable
masv settings multiconnect update --disable
```
* REST API
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/settings/multiconnect -d '{"enabled": true}'
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/settings/multiconnect -d '{"enabled": false}'
```
### Manage network interfaces
[Section titled “Manage network interfaces”](#manage-network-interfaces)
Enable or disable a specific multiconnect network interface:
* CLI
```bash
masv settings multiconnect interface {iface_id} --enable
masv settings multiconnect interface {iface_id} --disable
```
* REST API
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/settings/multiconnect/ifaces/{iface_id} -d '{"enabled": true}'
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/settings/multiconnect/ifaces/{iface_id} -d '{"enabled": false}'
```
***
## Network concurrency
[Section titled “Network concurrency”](#network-concurrency)
Control how many upload and download chunk workers are active at a time.
### Query concurrency settings
[Section titled “Query concurrency settings”](#query-concurrency-settings)
* CLI
```bash
masv settings network concurrency status
```
* REST API
```bash
curl http://localhost:8080/api/v1/settings/worker_config
```
### Update concurrency settings
[Section titled “Update concurrency settings”](#update-concurrency-settings)
* CLI
```bash
masv settings network concurrency update --download 8 --upload 8
```
* REST API
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/settings/worker_config -d '{"upload": 8, "download": 8}'
```
***
## Rate limits
[Section titled “Rate limits”](#rate-limits)
Rate limits restrict transfer bandwidth. A rate limit can apply at all times or on a schedule.
* With multiconnect **disabled**, the global rate-limit settings apply.
* With multiconnect **enabled**, the network interface-specific rate-limit settings apply.
### Query rate limits
[Section titled “Query rate limits”](#query-rate-limits)
* CLI
```bash
masv settings network rate-limit upload status
masv settings network rate-limit download status
```
* REST API
With multiconnect disabled, query the global rate limits:
```bash
curl http://localhost:8080/api/v1/settings/upload_rate_limit
curl http://localhost:8080/api/v1/settings/download_rate_limit
```
With multiconnect enabled, query a specific network interface:
```bash
curl http://localhost:8080/api/v1/settings/upload_rate_limit/{iface_id}
curl http://localhost:8080/api/v1/settings/download_rate_limit/{iface_id}
```
### Update rate limits
[Section titled “Update rate limits”](#update-rate-limits)
* CLI
With multiconnect disabled, update the global rate limit:
```bash
masv settings network rate-limit download update --bps 100000000 --enable
masv settings network rate-limit download update --disable
```
With multiconnect enabled, update the rate limit for a specific network interface:
```bash
masv settings network rate-limit download update --iface-id {iface_id} --enable
masv settings network rate-limit download update --iface-id {iface_id} --disable
```
* REST API
With multiconnect disabled, update the global rate limit:
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/settings/download_rate_limit -d '{
"rate_limit_bps": 100000000,
"enabled": true,
"schedule_enabled": false
}'
```
With multiconnect enabled, update the rate limit for a specific network interface:
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/settings/download_rate_limit/{iface_id} -d '{
"rate_limit_bps": 100000000,
"enabled": true,
"schedule_enabled": false
}'
```
### Rate-limit schedules
[Section titled “Rate-limit schedules”](#rate-limit-schedules)
Rate limits can be applied on a schedule, restricting bandwidth during specific days and times.
* CLI
With multiconnect disabled, enable or disable the global rate-limit schedule:
```bash
masv settings network rate-limit download schedule update --enable
masv settings network rate-limit download schedule update --disable
```
With multiconnect enabled, manage the schedule for a specific network interface:
```bash
masv settings network rate-limit download schedule update --iface-id {iface_id} --enable
masv settings network rate-limit download schedule update --iface-id {iface_id} --disable
```
Update days within the schedule (multiconnect disabled):
```bash
masv settings network rate-limit download schedule days \
--start "09:00" --end "17:00" --enable --mon --tue --wed --thu --fri
```
Update days within the schedule for a specific interface (multiconnect enabled):
```bash
masv settings network rate-limit download schedule days \
--iface-id "00:d8:61:be:e1:2c" --start "09:00" --end "17:00" --enable --sat --sun
```
* REST API
With multiconnect disabled, update the global rate limit with a schedule:
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/settings/upload_rate_limit -d '{
"rate_limit_bps": 100000000,
"enabled": true,
"schedule_enabled": true,
"schedule": {
"mon": { "enabled": true, "start": "09:00", "end": "17:00" },
"tue": { "enabled": true, "start": "09:00", "end": "17:00" },
"wed": { "enabled": true, "start": "09:00", "end": "17:00" },
"thu": { "enabled": true, "start": "09:00", "end": "17:00" },
"fri": { "enabled": true, "start": "09:00", "end": "17:00" },
"sat": { "enabled": false, "start": "09:00", "end": "17:00" },
"sun": { "enabled": false, "start": "09:00", "end": "17:00" }
}
}'
```
With multiconnect enabled, update the interface-specific rate limit with a schedule:
```bash
curl -H "Content-Type: application/json" -X POST \
http://localhost:8080/api/v1/settings/upload_rate_limit/{iface_id} -d '{
"rate_limit_bps": 100000000,
"enabled": true,
"schedule_enabled": true,
"schedule": {
"mon": { "enabled": true, "start": "09:00", "end": "17:00" },
"tue": { "enabled": true, "start": "09:00", "end": "17:00" },
"wed": { "enabled": true, "start": "09:00", "end": "17:00" },
"thu": { "enabled": true, "start": "09:00", "end": "17:00" },
"fri": { "enabled": true, "start": "09:00", "end": "17:00" },
"sat": { "enabled": false, "start": "09:00", "end": "17:00" },
"sun": { "enabled": false, "start": "09:00", "end": "17:00" }
}
}'
```
# Storage Gateway
> Connect storage devices to MASV Portals using Storage Gateway for automatic file delivery and remote access.
Storage Gateway lets you:
* Connect your storage to one or more MASV Portals and have files automatically delivered to it.
* Use the MASV Web App with Team members to access specific file locations on connected storage without requiring direct or remote access.
## Setup guide
[Section titled “Setup guide”](#setup-guide)
For an end-to-end walkthrough, see [How to integrate storage devices with MASV](https://help.massive.io/en/how-to-connect-a-storage-device-in-masv).
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
Storage Gateway requires MASV Agent to be installed on the server that provides storage. Choose one of the following installation methods:
* Native setup for [Linux](/agent/install-linux/), [macOS](/agent/install-macos/), or [Windows](/agent/install-windows/).
* [Docker setup](/agent/install-docker/).
Tip
If you’re running MASV Agent with Docker, run `masv` or `curl` commands inside the container. See the [Docker setup guide](/agent/install-docker/#executing-commands) for details.
***
## Add a connection
[Section titled “Add a connection”](#add-a-connection)
* CLI
```bash
# Store your gateway secret in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
masv gateway add \
--id "$GATEWAY_ID" \
--secret "$GATEWAY_SECRET" \
--name "$CONNECTION_NAME" \
--root-path "$SHARED_FOLDER" \
--permissions "$PERMISSIONS" \
--uid \
--gid
```
| Name | Type | Required | Description |
| ------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `id` | String | Yes | ID of the existing Storage Device Integration. |
| `name` | String | Yes | Connection name — use something descriptive and easy to recognize. |
| `permissions` | String | Yes | File permissions for this connection. Valid values: `r`, `w`. |
| `root-path` | String | Yes | This folder and everything within it becomes remotely accessible. Must be an absolute path. |
| `secret` | String | Yes | Unique secret key required to register this connection with the MASV API. |
| `gid` | Integer | No | Custom group ID override. Used to assign ownership of downloaded files on Linux. Not supported on Windows or read-only connections. |
| `uid` | Integer | No | Custom user ID override. Used to assign ownership of downloaded files on Linux. Not supported on Windows or read-only connections. |
Tip
Linux native installations run the background system service as root. If you want downloaded files to belong to a specific user, specify `gid` and `uid` overrides.
* REST API
```bash
curl -X POST -H "Content-Type: application/json" \
http://localhost:8080/api/v1/storage_gateway/register -d '{
"id": "$GATEWAY_ID",
"secret": "$GATEWAY_SECRET",
"name": "$CONNECTION_NAME",
"root_path": "$SHARED_FOLDER",
"permissions": "$PERMISSIONS",
"user_id": ,
"group_id":
}'
```
| Name | Type | Required | Description |
| ------------- | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
| `id` | String | Yes | ID of the existing Storage Device Integration. |
| `name` | String | Yes | Connection name — use something descriptive and easy to recognize. |
| `permissions` | String | Yes | File permissions for this connection. Valid values: `r`, `w`. |
| `root_path` | String | Yes | This folder and everything within it becomes remotely accessible. Must be an absolute path. |
| `secret` | String | Yes | Unique secret key required to register this connection with the MASV API. |
| `group_id` | Integer | No | Custom group ID override. Used to assign ownership of downloaded files on Linux. Not supported on Windows or read-only connections. |
| `user_id` | Integer | No | Custom user ID override. Used to assign ownership of downloaded files on Linux. Not supported on Windows or read-only connections. |
Tip
Linux native installations run the background system service as root. If you want downloaded files to belong to a specific user, specify `group_id` and `user_id` overrides.
***
## List connections
[Section titled “List connections”](#list-connections)
* CLI
```bash
masv gateway ls
```
* REST API
```bash
curl -X GET http://localhost:8080/api/v1/storage_gateway/list
```
***
## Remove a connection
[Section titled “Remove a connection”](#remove-a-connection)
* CLI
```bash
masv gateway rm CONNECTION_ID
```
* REST API
```bash
curl -X POST -H "Content-Type: application/json" \
http://localhost:8080/api/v1/storage_gateway/unregister -d '{
"id": "CONNECTION_ID"
}'
```
Tip
Removing a connection disconnects it from MASV Agent — it does not delete the Storage Device Integration from your MASV account.
***
## Update a connection
[Section titled “Update a connection”](#update-a-connection)
* CLI
```bash
masv gateway update id \
--root-path "$NEW_ROOT_PATH" \
--uid \
--gid
```
| Name | Type | Required | Description |
| ----------- | ------- | -------- | -------------------------------------------------------------- |
| `id` | String | Yes | ID of the existing Storage Device Integration. |
| `root-path` | String | No | New root folder. Must be an absolute path. |
| `gid` | Integer | No | Custom group ID override. Provide `-1` to remove the override. |
| `uid` | Integer | No | Custom user ID override. Provide `-1` to remove the override. |
* REST API
```bash
curl -X POST -H "Content-Type: application/json" \
http://localhost:8080/api/v1/storage_gateway/update -d '{
"id": "$CONNECTION_ID",
"root_path": "$NEW_ROOT_PATH",
"user_id": ,
"group_id":
}'
```
| Name | Type | Required | Description |
| ----------- | ------- | -------- | -------------------------------------------------------------- |
| `id` | String | Yes | ID of the existing Storage Device Integration. |
| `root_path` | String | No | New root folder. Must be an absolute path. |
| `group_id` | Integer | No | Custom group ID override. Provide `-1` to remove the override. |
| `user_id` | Integer | No | Custom user ID override. Provide `-1` to remove the override. |
***
## Next steps
[Section titled “Next steps”](#next-steps)
* **[Authentication](/agent/authentication/)** — Authenticate with API keys or credentials.
* **[Uploads](/agent/uploads/)** — Send files via Team email, shareable Link, or Portal.
* **[Downloads](/agent/downloads/)** — Initiate, monitor, and manage file downloads.
* **[Automations](/agent/automations/)** — Set up automated download and upload workflows.
# Upload files with the MASV Agent
> Upload files with MASV Agent via Team email, shareable Link, or Portal with CLI and REST API examples.
MASV Agent supports two types of uploads:
* **Team uploads** — Packages sent to email recipients or shared via a download Link. Requires a [valid user session](/agent/authentication/).
* **Portal uploads** — Packages sent to a specific MASV Portal. Does not require a user session.
All uploads are managed the same way after creation — only the creation step differs. Each upload transitions through these states:
| State | Description |
| -------------- | ---------------------------------------------------------------------------------------------- |
| `transferring` | The upload is currently transferring data. |
| `paused` | The upload is paused by the user. |
| `idle` | The upload has finished transferring data and can accept additional files or be finalized. |
| `complete` | The upload has been finalized and the Package has been sent to the intended destination. |
| `error` | A fatal error was encountered. This may or may not be recoverable depending on the error type. |
Caution
MASV Agent ignores the following files because they tend to change during the upload process, which causes failures: `desktop.ini`, `.DS_Store`, `.fcpcache`.
## Common parameters
[Section titled “Common parameters”](#common-parameters)
These parameters are supported by all upload types.
* CLI
| Name | Type | Required | Default | Description |
| ------------- | ------- | -------- | ------- | ----------------------------------------------------------------------------- |
| `chunk-size` | String | No | `""` | Custom chunk size for the upload. Overrides the server-level chunk-size flag. |
| `description` | String | No | `""` | Package description. |
| `name` | String | No | N/A | Package name. |
| `priority` | Integer | No | 0 | Upload priority. Higher values mean higher priority. |
* REST API
| Name | Type | Required | Default | Description |
| --------------------- | ------- | -------- | --------- | ----------------------------------------------------------------------------- |
| `chunk_size` | Integer | No | 104857600 | Target chunk size for the upload. Overrides the server-level chunk-size flag. |
| `package_description` | String | No | `""` | Package description. |
| `package_name` | String | No | N/A | Package name. |
| `paths` | Array | Yes | N/A | Array of file or directory paths to include in the upload. |
| `priority` | Integer | No | 0 | Upload priority. Higher values mean higher priority. |
***
## Send to email recipients
[Section titled “Send to email recipients”](#send-to-email-recipients)
Team Packages are sent to email recipients. A [valid user session](/agent/authentication/) is required.
Note
When an upload is initiated, MASV Agent starts uploading files immediately. After all files are uploaded, the upload transitions to `idle` state. In this state, the intended recipients **will not be notified**. To finalize the upload and transition it to `complete`, see [Finalize an upload](#finalize-an-upload).
* CLI
```bash
masv upload start email \
--emails='recipient1@domain.tld,recipient2@domain.tld' \
--team-id=$TEAM_ID \
--name="Optional package name" \
--description="Optional package description" \
/path/to/file/or/folder \
/path/to/another/file/or/folder
```
* REST API
```bash
curl -X POST -H "Content-Type: application/json" \
http://localhost:8080/api/v1/uploads -d '{
"team_id": "$TEAM_ID",
"paths": ["/path/to/file/or/folder", "/path/to/another/file/or/folder"],
"package_name": "Optional package name",
"package_description": "Optional package description",
"recipients": ["recipient1@domain.tld", "recipient2@domain.tld"],
"password": "optional_download_password"
}'
```
### Email-specific parameters
[Section titled “Email-specific parameters”](#email-specific-parameters)
Includes all [Common parameters](#common-parameters) and the shareable Link parameters below.
* CLI
| Name | Type | Required | Description |
| -------- | ------ | -------- | ---------------------------------------------------------------------------------- |
| `emails` | String | Yes | Comma-separated list of email recipients. Example: `'a@example.com,b@example.com'` |
* REST API
| Name | Type | Required | Description |
| ------------ | ----- | -------- | ----------------------------------------- |
| `recipients` | Array | Yes | Array of email recipients of the Package. |
***
## Send as a shareable Link
[Section titled “Send as a shareable Link”](#send-as-a-shareable-link)
Create a Team upload and generate a shareable download Link that can be shared with any recipient.
* CLI
```bash
masv upload start link \
--team-id=$TEAM_ID \
--name="Optional package name" \
--description="Optional package description" \
/path/to/file/or/folder \
/path/to/another/file/or/folder
```
If successful, MASV Agent starts the upload and outputs a shareable Link:
```text
Upload 5c8b2cd9-55fc-4462-9c01-87f4df03814a started. Shareable link: https://get.massive.io/{LINK_ID}?secret={SECRET}
```
The Link becomes active as soon as the upload finalizes. You can create additional shareable Links for existing uploads:
```bash
masv upload link $UPLOAD_ID
```
* REST API
To create a Link, the upload must be in the `complete` state and the upload type must be `team`:
```bash
curl -X POST -H "Content-Type: application/json" \
http://localhost:8080/api/v1/uploads/{UPLOAD_ID}/link
```
Optional fields can be specified in the POST body:
```bash
curl -X POST -H "Content-Type: application/json" \
http://localhost:8080/api/v1/uploads/{UPLOAD_ID}/link -d '{
"active": "true",
"access_limit": 10,
"email": "recipient@domain.tld",
"expiry": "2021-06-18T10:00:00Z",
"password": "PASSWORD"
}'
```
The response includes the Link ID and secret:
```json
{
"email": "me@domain.tld",
"expiry": "2020-12-31T23:59:59.999",
"locked": false,
"id": "01ECSWWC8R6J1N8Y46S094CRGT",
"secret": "dDaNpsSTqlRDnTBe"
}
```
Construct the download URL: `https://get.massive.io/{id}?secret={secret}`
### Shareable Link parameters
[Section titled “Shareable Link parameters”](#shareable-link-parameters)
Includes all [Common parameters](#common-parameters).
* CLI
| Name | Type | Required | Default | Description |
| ------------------- | ------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------ |
| `delete-after` | Integer | No | N/A | Number of days to keep the Package in MASV storage. |
| `download-limit` | Integer | No | N/A | Default access limit for download Links created for this Package. |
| `password` | String | No | N/A | Default password for download Links created for this Package. |
| `tag-name` | String | No | N/A | Name of the Package’s tag. Created if it doesn’t already exist. |
| `tag-id` | String | No | N/A | ID of an existing Package tag. |
| `team-id` | String | Yes | N/A | Team ID to associate with this upload. |
| `teamspace-id` | String | No | N/A | Teamspace ID to associate with this upload. If not specified, the upload is associated with the entire Team. |
| `unlimited-storage` | Boolean | No | false | Enables unlimited storage for the Package. |
* REST API
| Name | Type | Required | Default | Description |
| ------------------- | ------- | -------- | ------- | ------------------------------------------------------------------------------------------------------------ |
| `delete_after` | Integer | No | N/A | Number of days to keep the Package in storage. |
| `download_limit` | Integer | No | N/A | Default access limit for download Links created for this Package. |
| `password` | String | No | N/A | Default password for download Links created for this Package. |
| `tag` | Object | No | N/A | Tag object. |
| `team_id` | String | Yes | N/A | Team ID to associate with this upload. |
| `teamspace_id` | String | No | N/A | Teamspace ID to associate with this upload. If not specified, the upload is associated with the entire Team. |
| `unlimited_storage` | Boolean | No | false | Enables unlimited storage for the Package. |
***
## Send to a Portal
[Section titled “Send to a Portal”](#send-to-a-portal)
MASV Agent can upload Packages to any MASV Portal. This does not require a user session.
Note
When an upload is initiated, MASV Agent starts uploading files immediately. After all files are uploaded, the upload transitions to `idle` state. In this state, the intended recipients **will not be notified**. To finalize the upload and transition it to `complete`, see [Finalize an upload](#finalize-an-upload).
* CLI
```bash
masv upload start portal \
--subdomain=$PORTAL_SUBDOMAIN \
--sender='your-email@domain.tld' \
--name="Optional package name" \
--description="Optional package description" \
/path/to/file/or/folder \
/path/to/another/file/or/folder
```
* REST API
```bash
curl -X POST -H "Content-Type: application/json" \
http://localhost:8080/api/v1/portals/uploads -d '{
"subdomain": "$PORTAL_SUBDOMAIN",
"sender_email": "me@domain.tld",
"paths": ["/path/to/file/or/folder", "/path/to/another/file/or/folder"],
"access_code": "optional_access_code",
"package_name": "Optional package name",
"package_description": "Optional package description"
}'
```
### Portal-specific parameters
[Section titled “Portal-specific parameters”](#portal-specific-parameters)
Includes all [Common parameters](#common-parameters).
* CLI
| Name | Type | Required | Default | Description |
| ----------- | ------ | -------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
| `metadata` | String | No | N/A | Key-value pairs to submit as Portal metadata: `key1=value1,key2=value2` |
| `password` | String | No | N/A | Portal access code; required for password-protected Portals. |
| `sender` | String | Yes | N/A | The sender’s email address. |
| `subdomain` | String | Yes | N/A | Target Portal subdomain. For example, if the Portal URL is `https://acme.portal.massive.io`, the subdomain is `acme`. |
* REST API
| Name | Type | Required | Default | Description |
| -------------- | ------ | -------- | ------- | --------------------------------------------------------------------------------------------------------------------- |
| `access_code` | String | No | N/A | Portal access code; required for password-protected Portals. |
| `metadata` | Object | No | N/A | JSON key-value pairs to submit as Portal metadata. Example: `{ "key1": "value1", "key2": "value2" }` |
| `sender_email` | String | Yes | N/A | The sender’s email address. |
| `subdomain` | String | Yes | N/A | Target Portal subdomain. For example, if the Portal URL is `https://acme.portal.massive.io`, the subdomain is `acme`. |
***
## Finalize an upload
[Section titled “Finalize an upload”](#finalize-an-upload)
After all files in a package are uploaded, the upload transitions to the `idle` state and the package is automatically finalized. After finalization, no more files can be added, and the upload transitions to `complete`. When an upload is `complete`, the intended recipients are notified.
To disable autofinalize, start the server with `masv server start --auto-finalize=false`, then finalize manually:
* CLI
```bash
masv upload finalize $UPLOAD_ID
```
* REST API
```bash
curl -X POST http://localhost:8080/api/v1/uploads/{UPLOAD_ID}/finalize
```
***
## View upload status
[Section titled “View upload status”](#view-upload-status)
List all uploads managed by MASV Agent:
* CLI
```bash
masv upload ls
```
* REST API
```bash
curl -X GET http://localhost:8080/api/v1/uploads
```
View full details for a specific upload, including individual file states:
* CLI
```bash
masv upload status $UPLOAD_ID
```
* REST API
```bash
curl -X GET http://localhost:8080/api/v1/uploads/{UPLOAD_ID}
```
***
## Manage uploads
[Section titled “Manage uploads”](#manage-uploads)
MASV Agent starts uploading file data at the time of upload creation. Uploads can be paused, resumed, and deleted.
### Pause an upload
[Section titled “Pause an upload”](#pause-an-upload)
* CLI
```bash
masv upload pause $UPLOAD_ID
```
* REST API
```bash
curl -X POST http://localhost:8080/api/v1/uploads/{UPLOAD_ID}/pause
```
### Pause all uploads
[Section titled “Pause all uploads”](#pause-all-uploads)
* CLI
```bash
masv upload pause all
```
* REST API
```bash
curl -X POST http://localhost:8080/api/v1/uploads/pause
```
### Resume an upload
[Section titled “Resume an upload”](#resume-an-upload)
* CLI
```bash
masv upload resume $UPLOAD_ID
```
* REST API
```bash
curl -X POST http://localhost:8080/api/v1/uploads/{UPLOAD_ID}/resume
```
### Resume all uploads
[Section titled “Resume all uploads”](#resume-all-uploads)
* CLI
```bash
masv upload resume all
```
* REST API
```bash
curl -X POST http://localhost:8080/api/v1/uploads/resume
```
### Delete an upload
[Section titled “Delete an upload”](#delete-an-upload)
* CLI
```bash
masv upload rm $UPLOAD_ID
```
* REST API
```bash
curl -X DELETE http://localhost:8080/api/v1/uploads/{UPLOAD_ID}
```
Caution
Deleting a complete upload does not automatically delete the Package from MASV cloud storage. It may still incur download or storage charges. To delete a Package, see the [Packages API reference](/api/packages/).
### Delete all uploads
[Section titled “Delete all uploads”](#delete-all-uploads)
* CLI
```bash
masv upload rm all
```
* REST API
```bash
curl -X DELETE http://localhost:8080/api/v1/uploads
```
### Delete uploads by state
[Section titled “Delete uploads by state”](#delete-uploads-by-state)
```bash
curl -X DELETE http://localhost:8080/api/v1/uploads?states=complete,error
```
***
## Next steps
[Section titled “Next steps”](#next-steps)
* **[Downloads](/agent/downloads/)** — Initiate, monitor, and manage file downloads.
* **[Automations](/agent/automations/)** — Set up automated download and upload workflows.
* **[Storage Gateway](/agent/storage-gateway/)** — Connect storage devices to MASV.
# MASV API
> Integrate MASV into your applications with the REST API for managing teams, portals, packages, and transfers.
The MASV API lets you programmatically manage file transfers, teams, portals, and packages. Use it to build automated workflows and custom integrations.
## Guides
[Section titled “Guides”](#guides)
* **[Getting Started](/api/getting-started/)** — Authentication, system model, and your first transfer.
* **[Core Concepts](/api/core-concepts/)** — Teams, Portals, Packages, Links, Teamspaces, and Metadata.
## Reference
[Section titled “Reference”](#reference)
* **[Authorization](/api/authorization/)** — Authenticate API requests.
* **[API Keys](/api/api-keys/)** — Create and manage API keys.
* **[Web Tokens](/api/tokens/)** — Scoped tokens for package and transfer operations.
* **[Uploads](/api/uploads/)** — Upload files and packages.
* **[Downloads](/api/downloads/)** — Download files and packages.
* **[Packages](/api/packages/)** — Manage transfer packages.
* **[Links](/api/links/)** — Create and manage shareable links.
* **[Portals](/api/portals/)** — Configure upload portals.
* **[Filters](/api/filters/)** — Filter and query resources.
* **[Cloud Connections](/api/cloud-connections/)** — Connect external storage providers.
* **[Webhooks](/api/webhooks/)** — Receive event notifications.
* **[Custom Metadata](/api/custom-metadata/)** — Attach metadata to portals and packages.
* **[Tags](/api/tags/)** — Organize resources with tags.
* **[Teamspaces](/api/teamspaces/)** — Manage shared workspaces.
# API keys
> Create and use MASV API keys for simplified authorization without passwords or MFA.
Use an API key to authorize your requests to the MASV API.
API keys simplify integration with the MASV API in these ways:
* The username and password do not need to be hardcoded or stored.
* The key does not require a refresh after expiration every few days.
* An API key, while active, does not require the user to periodically answer multi-factor authentication (MFA) challenges.
## Supported roles
[Section titled “Supported roles”](#supported-roles)
An API key gives your application the same permissions as the MASV user from which it was created. Users with these roles can create API keys:
* Owner
* Admin
* [Custom role](https://help.massive.io/en/roles-and-permissions-in-masv#custom-roles) with API permissions
Admins can manage only the API keys they create, Owners have full access to manage all API keys for the Team, and Custom roles vary according their permission settings. To learn more about user roles and permissions, see [Roles and permissions in MASV](https://help.massive.io/en/roles-and-permissions-in-masv#custom-roles).
## Create an API key
[Section titled “Create an API key”](#create-an-api-key)
Create and manage API keys in the [MASV Web App](https://help.massive.io/en/how-to-create-and-manage-api-keys-in-masv). You can create API keys for any Team you belong to.
Note
The API key value is generated automatically and returned only once. It carries the same privileges as the user who created it. Store it securely — you cannot retrieve the key value later.
API key availability depends on your Team’s billing status:
Note
If the Team’s account becomes inactive due to non-payment, MASV suspends the Team’s API keys until account billing is reactivated. Attempting to use a suspended key returns error `402 unauthorized`. MASV also suspends API keys when a Trial account ends. For details, see [pricing](https://masv.io/pricing/).
## Use an API key
[Section titled “Use an API key”](#use-an-api-key)
Supply your API key in the `X-API-KEY` header on any request to the MASV API.
### Example: Listing sent packages
[Section titled “Example: Listing sent packages”](#example-listing-sent-packages)
For full package documentation, see [Packages](/api/packages/).
| Method | Route |
| :----- | :-------------------------- |
| `GET` | `/teams/{team_id}/packages` |
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -H "X-API-KEY: $MASV_API_KEY" \
-H "Content-Type: application/json" \
-X GET https://api.massive.app/v1/teams/$TEAM_ID/packages
```
Tip
Rotate your API keys at least every 90 days. If you suspect a key has been compromised, deactivate it immediately, create a new key, and review your Team’s recent API activity.
# Authorization
> Learn how the MASV API authorizes requests using API keys with TLS 1.2+ encryption.
Authorize your MASV API requests using [API keys](/api/api-keys/).
API keys are the only supported authorization method for MASV API integrations. They simplify your application, are easy to rotate, and do not require storing user credentials.
The MASV API also uses scoped [JSON Web Tokens](/api/tokens/) internally for package and transfer operations. These tokens are returned automatically by the API when you interact with packages and cloud connections — you do not need to generate them yourself.
All MASV API connections require Transport Layer Security (TLS) 1.2 or 1.3.
# Cloud connections
> Create, manage, and attach cloud storage connections to MASV Portals for automated file delivery to 20+ providers.
Connect your Portals to an external cloud storage provider using Cloud Connect.
You can create cloud connections for storage device integrations by installing the MASV Agent on the device and configuring a [storage gateway](/agent/storage-gateway/). See [Provider examples](#provider-examples) for information about specific storage devices.
## Create cloud connection
[Section titled “Create cloud connection”](#create-cloud-connection)
| Method | Route |
| :----- | :----------------------------------- |
| `POST` | `/teams/{team_id}/cloud_connections` |
### Headers
[Section titled “Headers”](#headers)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Body
[Section titled “Body”](#body)
| Name | Type | Required | Description |
| -------------------------- | --------- | -------- | ----------------------------------------------------------------------- |
| `name` | String | No | Name of the connection |
| `provider` | String | Yes | Cloud storage provider |
| `direction` | String | No | `masv_to_cloud` or `cloud_to_masv`. Default: `masv_to_cloud` |
| `authorization` | Object | Yes | Authorization credentials, specific to each provider |
| `target_directory_id` | String | No | Custom directory path for package files (masv\_to\_cloud only) |
| `source_directory_ids` | String\[] | No | Source directories for filepicker (cloud\_to\_masv only) |
| `upload_transfer_manifest` | Boolean | No | Generate a JSON manifest file for transfers (S3 and S3-compatible only) |
### Request (MASV to cloud)
[Section titled “Request (MASV to cloud)”](#request-masv-to-cloud)
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -d '{"name": "$NAME", "provider": "$PROVIDER", "direction": "masv_to_cloud", "authorization": {"":""}, "target_directory_id":"$TARGET_DIR"}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/teams/$TEAM_ID/cloud_connections
```
### Request (Cloud to MASV)
[Section titled “Request (Cloud to MASV)”](#request-cloud-to-masv)
For the `cloud_to_masv` direction, your application can send from the `amazon_s3` and `wasabi_s3` providers.
```bash
curl -d '{"name": "$NAME", "provider": "$PROVIDER", "direction": "cloud_to_masv", "authorization": {"":""}, "source_directory_ids":["$SOURCE_DIR"]}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/teams/$TEAM_ID/cloud_connections
```
### Response
[Section titled “Response”](#response)
Returns `201 Created`:
```json
{
"created_at": "2020-10-06T12:44:19.369Z",
"direction": "masv_to_cloud",
"id": "01EKYZ1VH9GTS9GYMCMSR4NR9G",
"name": "Sample Connection",
"provider": "amazon_s3",
"state": "ok",
"updated_at": "2020-10-06T12:44:19.369Z"
}
```
## Provider examples
[Section titled “Provider examples”](#provider-examples)
MASV integrates with a growing list of cloud storage providers. All provider authorization fields are required unless noted otherwise.
### Amazon S3
[Section titled “Amazon S3”](#amazon-s3)
Provider value: `amazon_s3`
MASV supports transfers to or from an Amazon S3 bucket in any region. You must create an IAM policy with the required permissions.
**masv\_to\_cloud permissions:** `s3:PutObject`, `s3:AbortMultipartUpload`, `s3:ListBucket`, `s3:DeleteObject`, `s3:GetBucketLocation`
**cloud\_to\_masv permissions:** `s3:GetObject`, `s3:ListBucket`
#### Key-based access
[Section titled “Key-based access”](#key-based-access)
Attach the IAM policy to an IAM user and create an access key.
#### Role-based access
[Section titled “Role-based access”](#role-based-access)
Attach the IAM policy to an IAM role with a trust policy allowing MASV to assume the role. This model does not require routine key rotation.
#### Authorization
[Section titled “Authorization”](#authorization)
| Name | Type | Description |
| --------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `client_id` | String | Access key ID (key-based access only) |
| `client_secret` | String | Access key secret (key-based access only) |
| `role_arn` | String | ARN of the IAM role (role-based access only) |
| `external_id` | String | External ID in trust policy (role-based access only) |
| `destination` | String | Bucket name |
| `region` | String | AWS region (for example, `us-east-1`) |
| `storage_class` | String | S3 storage class: `STANDARD`, `INTELLIGENT_TIERING`, `STANDARD_IA`, `ONEZONE_IA`, `GLACIER_IR`, `GLACIER`, or `DEEP_ARCHIVE`. Default: `STANDARD` |
### Amazon EBS/EFS
[Section titled “Amazon EBS/EFS”](#amazon-ebsefs)
Provider value: `amazon_efs_sg`
Create a cloud connection with Amazon EBS or EFS storage that has the MASV Agent installed via a [storage gateway](/agent/storage-gateway/).
Note
Storage gateway connections do not pass authorization. The MASV API generates the credentials for the MASV Agent.
### Azure Storage
[Section titled “Azure Storage”](#azure-storage)
Provider value: `azure`
MASV supports uploading to an Azure storage container using a Shared Access Signature (SAS) URI with `Blob` service access on `Object` and `Container` resource types.
| Name | Type | Description |
| ---------------------- | ------ | ---------------------- |
| `blob_service_sas_url` | String | A Blob service SAS URL |
| `destination` | String | Name of the container |
### Backblaze B2
[Section titled “Backblaze B2”](#backblaze-b2)
Provider value: `backblazeb2`
| Name | Type | Description |
| --------------- | ------ | -------------------------------- |
| `client_id` | String | Key ID with access to the bucket |
| `client_secret` | String | Application key |
| `destination` | String | Bucket name |
### Box
[Section titled “Box”](#box)
Provider value: `box`
| Name | Type | Description |
| --------------- | ------ | ----------------------------------- |
| `client_id` | String | Client ID from Box keypair JSON |
| `client_secret` | String | Client secret from Box keypair JSON |
| `public_key_id` | String | Public key ID |
| `private_key` | String | Private key (full PEM format) |
| `passphrase` | String | Passphrase |
| `enterprise_id` | String | Enterprise ID |
### Desktop Mounted Storage
[Section titled “Desktop Mounted Storage”](#desktop-mounted-storage)
Provider value: `desktop_sg`
Connect via MASV Desktop App with a [storage gateway](/agent/storage-gateway/).
### DigitalOcean
[Section titled “DigitalOcean”](#digitalocean)
Provider value: `digital_ocean_s3`
| Name | Type | Description |
| --------------- | ------ | ------------------------------------------------------- |
| `client_id` | String | Access key ID |
| `client_secret` | String | Access key secret |
| `destination` | String | Bucket name |
| `endpoint` | String | Region URL (for example, `sfo2.digitaloceanspaces.com`) |
| `region` | String | Region (for example, `sfo2`) |
### Frame.io
[Section titled “Frame.io”](#frameio)
Provider value: `frameio_v4`
| Name | Type | Description |
| --------------- | ------ | ------------------------------- |
| `auth_strategy` | String | Must be `server` |
| `client_id` | String | Adobe S2S project client ID |
| `client_secret` | String | Adobe S2S project client secret |
| `account_id` | String | Frame.io account ID |
| `workspace_id` | String | Frame.io workspace ID |
| `project_id` | String | Frame.io project ID |
### Frame.io (Legacy)
[Section titled “Frame.io (Legacy)”](#frameio-legacy)
Provider value: `frameio`
| Name | Type | Description |
| ----------------- | ------ | ------------------------ |
| `developer_token` | String | Frame.io developer token |
| `account_id` | String | Frame.io account ID |
| `team_id` | String | Frame.io team ID |
| `project_id` | String | Frame.io project ID |
### Google Cloud Storage
[Section titled “Google Cloud Storage”](#google-cloud-storage)
Provider value: `google_cloud_storage`
| Name | Type | Description |
| -------------- | ------ | --------------------------------------- |
| `client_email` | String | Client email of the GCS Service Account |
| `private_key` | String | Private key (full PEM format) |
| `destination` | String | Bucket name |
### IBM Cloud Object Storage
[Section titled “IBM Cloud Object Storage”](#ibm-cloud-object-storage)
Provider value: `ibm_cloud_s3`
| Name | Type | Description |
| --------------- | ------ | ------------------------------- |
| `client_id` | String | Access key ID |
| `client_secret` | String | Access key secret |
| `destination` | String | Bucket name |
| `endpoint` | String | Region URL |
| `region` | String | Region (for example, `us-east`) |
### Iconik
[Section titled “Iconik”](#iconik)
Provider value: `iconik`
| Name | Type | Description |
| ------------------- | ------ | ------------------------- |
| `application_id` | String | Iconik application ID |
| `application_token` | String | Iconik application token |
| `storage_id` | String | Iconik storage account ID |
### Jellyfish
[Section titled “Jellyfish”](#jellyfish)
Provider value: `jellyfish_sg`
Connect via MASV Agent with a [storage gateway](/agent/storage-gateway/).
### Minio
[Section titled “Minio”](#minio)
Provider value: `minio_s3`
| Name | Type | Description |
| --------------------- | ------ | ------------------------------ |
| `client_id` | String | Access key ID |
| `client_secret` | String | Access key secret |
| `destination` | String | Bucket name |
| `endpoint` | String | URL for the bucket |
| `region` | String | Region (typically `default`) |
| `force_s3_path_style` | String | Whether to use path-style URLs |
### Object Matrix
[Section titled “Object Matrix”](#object-matrix)
Provider value: `object_matrix_s3`
| Name | Type | Description |
| --------------- | ------ | ------------------------------------------------------ |
| `client_id` | String | Access key ID |
| `client_secret` | String | Access key secret |
| `destination` | String | Bucket name |
| `endpoint` | String | URL (for example, `spacename.matrixstore.cloud:12323`) |
| `region` | String | Must be `eu` |
### OpenDrives
[Section titled “OpenDrives”](#opendrives)
Provider value: `opendrives_sg`
Connect via MASV Agent with a [storage gateway](/agent/storage-gateway/).
### PostLab
[Section titled “PostLab”](#postlab)
Provider value: `postlab`
| Name | Type | Description |
| --------- | ------ | ---------------- |
| `team_id` | String | PostLab team key |
### QNAP
[Section titled “QNAP”](#qnap)
Provider value: `qnap_sg`
Connect via MASV Agent with a [storage gateway](/agent/storage-gateway/).
### Seagate Lyve Cloud
[Section titled “Seagate Lyve Cloud”](#seagate-lyve-cloud)
Provider value: `lyve_cloud_s3`
| Name | Type | Description |
| --------------- | ------ | ----------------- |
| `client_id` | String | Access key ID |
| `client_secret` | String | Access key secret |
| `destination` | String | Bucket name |
| `endpoint` | String | Region URL |
| `region` | String | Region |
### Storj
[Section titled “Storj”](#storj)
Provider value: `storj_s3`
| Name | Type | Description |
| --------------- | ------ | -------------------------------------------------- |
| `client_id` | String | Access key ID |
| `client_secret` | String | Access key secret |
| `destination` | String | Bucket name |
| `endpoint` | String | URL (for example, `https://gateway.storjshare.io`) |
| `region` | String | Should be `default` |
### Synology
[Section titled “Synology”](#synology)
Provider value: `synology_sg`
Connect via MASV Agent with a [storage gateway](/agent/storage-gateway/).
### TrueNAS
[Section titled “TrueNAS”](#truenas)
Provider value: `truenas_sg`
Connect via MASV Agent with a [storage gateway](/agent/storage-gateway/).
### Wasabi
[Section titled “Wasabi”](#wasabi)
Provider value: `wasabi_s3`
| Name | Type | Description |
| --------------- | ------ | ------------------------------------------------------ |
| `client_id` | String | Access key ID |
| `client_secret` | String | Access key secret |
| `destination` | String | Bucket name |
| `endpoint` | String | Region URL (for example, `s3.us-east-1.wasabisys.com`) |
| `region` | String | Region |
### Other S3-compatible storage
[Section titled “Other S3-compatible storage”](#other-s3-compatible-storage)
Provider value: `generic_s3`
| Name | Type | Description |
| --------------------- | ------ | ------------------------------ |
| `client_id` | String | Access key ID |
| `client_secret` | String | Access key secret |
| `destination` | String | Bucket name |
| `endpoint` | String | URL for the bucket |
| `region` | String | Region (use `default` if none) |
| `force_s3_path_style` | String | Whether to use path-style URLs |
### Other Storage Devices
[Section titled “Other Storage Devices”](#other-storage-devices)
Provider value: `storage_gateway`
Connect any NAS or SAN with the MASV Agent installed via a [storage gateway](/agent/storage-gateway/).
## List cloud connections
[Section titled “List cloud connections”](#list-cloud-connections)
| Method | Route |
| :----- | :----------------------------------- |
| `GET` | `/teams/{team_id}/cloud_connections` |
### Request
[Section titled “Request”](#request)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X GET https://api.massive.app/v1/teams/$TEAM_ID/cloud_connections
```
### Response
[Section titled “Response”](#response-1)
Returns `200 OK` with an array of connection objects.
## Update cloud connection
[Section titled “Update cloud connection”](#update-cloud-connection)
| Method | Route |
| :----- | :----------------------------------- |
| `PUT` | `/cloud_connections/{connection_id}` |
### Body
[Section titled “Body”](#body-1)
| Name | Type | Required | Description |
| --------------- | ------ | -------- | ------------------------- |
| `name` | String | No | Name of the connection |
| `authorization` | Object | No | Authorization credentials |
Any authorization properties not provided are assumed unchanged.
### Request
[Section titled “Request”](#request-1)
```bash
curl -d '{"name": "$NAME", "authorization": {"":""}}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X PUT https://api.massive.app/v1/cloud_connections/$CONNECTION_ID
```
### Response
[Section titled “Response”](#response-2)
Returns `200 OK` with the updated connection object.
## Delete cloud connection
[Section titled “Delete cloud connection”](#delete-cloud-connection)
| Method | Route |
| :------- | :----------------------------------- |
| `DELETE` | `/cloud_connections/{connection_id}` |
### Request
[Section titled “Request”](#request-2)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X DELETE https://api.massive.app/v1/cloud_connections/$CONNECTION_ID
```
### Response
[Section titled “Response”](#response-3)
Returns `204 No Content`. In-progress transfers will complete normally.
## Attach cloud connections to Portals
[Section titled “Attach cloud connections to Portals”](#attach-cloud-connections-to-portals)
Manage cloud connections by updating the Portal with a list of connections to enable.
| Method | Route |
| :----- | :--------------------- |
| `PUT` | `/portals/{portal_id}` |
### Body
[Section titled “Body”](#body-2)
| Name | Type | Required | Description |
| ----------------------------- | --------- | -------- | ------------------------------------------------------------------------ |
| `cloud_connections` | Object\[] | No | List of connections with `id`, `target_action`, and optional `filter_id` |
| `configure_cloud_connections` | Boolean | Yes | Must be `true` to update connections |
### Request
[Section titled “Request”](#request-3)
```bash
curl -d '{"name": "$NAME", "subdomain": "$SUBDOMAIN", "configure_cloud_connections": true, "cloud_connections": [{"id":"", "target_action":"transfer"}], "active": true, "has_access_code": false}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X PUT https://api.massive.app/v1/portals/$PORTAL_ID
```
Caution
All attached cloud connections must be provided in each update or they are removed.
## Initiate a manual package transfer
[Section titled “Initiate a manual package transfer”](#initiate-a-manual-package-transfer)
| Method | Route |
| :----- | :-------------------------------- |
| `POST` | `/packages/{package_id}/transfer` |
### Headers
[Section titled “Headers”](#headers-1)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | ---------------------- |
| `X-Package-Token` | String | Yes | Package JSON Web Token |
### Body
[Section titled “Body”](#body-3)
| Name | Type | Required | Description |
| --------------------- | --------- | -------- | ---------------------------------------------------- |
| `cloud_connection_id` | String | Yes | ID of the cloud connection |
| `notify_email` | String | No | Email to notify when transfer completes |
| `files` | Object\[] | No | Files/directories to transfer (cloud\_to\_masv only) |
### Request (MASV to cloud)
[Section titled “Request (MASV to cloud)”](#request-masv-to-cloud-1)
```bash
curl -H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/packages/$PACKAGE_ID/transfer \
-d '{"notify_email": "$EMAIL", "cloud_connection_id": "$CONNECTION_ID"}'
```
# Core concepts
> Understand the MASV data model — Teams, Users, Roles, Packages, Files, Links, Portals, Teamspaces, and Metadata.
MASV organizes file transfer workflows around a small set of core objects. Understanding how these objects relate helps you choose the right integration pattern and navigate the API effectively.
This page is a conceptual overview. For hands-on steps, see [Getting Started](/api/getting-started/).
MASV Data Model
Diagram source
```plaintext
erDiagram
Team ||--o{ Member : has
Team ||--o{ Teamspace : has
Team ||--o{ Portal : has
Team ||--o{ "Integration / Cloud Connection" : has
Member }o--o{ Teamspace : "belongs to"
Teamspace ||--|| Portal : "exposes"
Portal ||--o{ Package : receives
Package ||--o{ Link : has
```
The MASV data model diagram shows the relationships between core entities. A Team has Members, Teamspaces, Portals, and Integration / Cloud Connections. Members and Teamspaces have a many-to-many relationship. Each Teamspace maps to one Portal. A Portal receives Packages, and a Package has many Links.
## Teams
[Section titled “Teams”](#teams)
A Team is the top-level organizational boundary in MASV. Most objects — Portals, Teamspaces, Packages, Users, and API keys — belong to a Team directly or indirectly.
When designing an integration, treat the Team as the root scope for configuration and ownership. Your application typically starts by identifying the Team it operates against, then creates or queries resources within that Team.
## Users and Roles
[Section titled “Users and Roles”](#users-and-roles)
Users act within a Team according to their assigned role. MASV authorization is role-aware: an API key carries the same privileges as the user who created it.
Key role behaviors:
* **Owner** — Full administrative control. Can manage all API keys for the Team.
* **Admin** — Can create and manage their own API keys. Has broad access to Team resources.
* [Custom role](https://help.massive.io/en/roles-and-permissions-in-masv#custom-roles) — Accounts that have access to create Custom roles can choose to create one or more roles with API permissions.
Authorization in MASV is not only about whether a request is authenticated – it also depends on whether the user’s role permits the action. Portal creation, teamspace visibility, and API key management are all subject to role-based access.
For details on authentication mechanisms, see [Authorization](/api/authorization/).
## Packages
[Section titled “Packages”](#packages)
A Package is the core transfer object in MASV. It represents a set of files sent by a Team member or received through a Portal.
You don’t upload “into MASV” in the abstract — you upload files into a specific Package. That Package then becomes the unit you manage, share, download, expire, or inspect. Think of it as a virtual directory that holds one transfer’s worth of content.
Packages can contain one or many Files, carry metadata, and be shared with recipients through Links.
For the upload and download workflows, see [Uploads](/api/uploads/) and [Downloads](/api/downloads/).
## Files
[Section titled “Files”](#files)
Files are the contents of a Package. A Package may include one or many Files, and file metadata is represented at the API layer while the underlying file data is stored in MASV’s storage infrastructure.
This separation is useful for integrations. Your code interacts with Package and File metadata through API resources, while the actual upload and download operations are carried out through authorized transfer flows and MASV-managed storage endpoints.
## Links
[Section titled “Links”](#links)
A Link represents a share of a Package to one or more recipients. A Package can have multiple Links, each granting download access.
The only way to download a Package is to obtain a Link from a Package owner or to create a direct-download Link (which requires Package management access). Recipient Link credentials are supplied out of band rather than exposed through the API.
This is an important security boundary: Package ownership and Link possession are distinct concepts. Your application may create or manage Packages, but download access is intentionally mediated through Link-based mechanisms.
For Link management, see [Links](/api/links/).
## Portals
[Section titled “Portals”](#portals)
A Portal is MASV’s external-facing upload surface. Each Portal has a unique subdomain and is created under a Team.
Portals let external contributors submit files into your MASV environment without exposing your internal administration model. They serve as both a user experience surface and an integration boundary.
### Use cases
[Section titled “Use cases”](#use-cases)
* **Vendor delivery** — Standardize how incoming content is received from external partners.
* **Client submissions** — Collect files from clients without requiring them to have a MASV account.
* **Media ingest** — Receive content from creators and route it to downstream workflows.
### Portal security
[Section titled “Portal security”](#portal-security)
Portal access is configurable. Options include upload access codes, upload and download password protection, Teamspace assignment, and private Portal access rules. Some Portals are broadly reachable with minimal friction; others are tightly controlled for authenticated users or Teamspace members.
Treat Portal security as part of your business workflow, not as a fixed platform behavior.
For Portal configuration, see [Portals](/api/portals/).
## Teamspaces
[Section titled “Teamspaces”](#teamspaces)
A Teamspace is a way to organize a subset of Team members into a smaller group. Teamspaces serve as both an organizational construct and an access-control mechanism — use them to group people by project, client, or department.
Where a Team gives you the enterprise boundary, a Teamspace gives you the project boundary. If your application manages multiple productions, vendors, customers, or departments, Teamspaces are the natural place to mirror that structure.
For example, you might create one Teamspace per show, client account, or ingest workflow. Packages, Portals, and membership can then be aligned so users see only the work relevant to them.
For Teamspace management, see [Teamspaces](/api/teamspaces/).
## Metadata
[Section titled “Metadata”](#metadata)
Metadata in MASV is structured information collected from senders during Portal uploads. A Portal can have a custom metadata form, and when that form exists, a form response is required before the Portal Package can be created.
### Metadata forms
[Section titled “Metadata forms”](#metadata-forms)
An admin creates a form, configures its fields, the sender submits a response, and the Package is created with that response attached. When the Package is finalized, the metadata is delivered in JSON, CSV, XML, or email-body format.
### Field types
[Section titled “Field types”](#field-types)
MASV provides default fields for new metadata forms: **Sender Email**, **Package Name**, and **Package Description**. Forms can also collect data through checkbox, dropdown select, radio button, email, and date picker fields.
Use metadata fields not only for human-readable context, but also for operational values — routing codes, production IDs, file categories, delivery targets, or downstream workflow selectors.
### Integration considerations
[Section titled “Integration considerations”](#integration-considerations)
Metadata is a first-class integration surface. Treat it as structured input to downstream automation, storage routing, indexing, or MAM ingestion, rather than a note attached to a transfer.
Note
MASV Agent automations do not support Portals with custom metadata forms when user interaction is required to fill them out. If your workflow depends on unattended automation, design your intake path accordingly.
For metadata configuration, see [Custom Metadata](/api/custom-metadata/).
## How the concepts fit together
[Section titled “How the concepts fit together”](#how-the-concepts-fit-together)
Control Plane vs. Data Plane
Diagram source
```plaintext
flowchart TD
App["Your Application"]
subgraph ControlPlane["Control Plane — MASV API"]
API["REST API\nhttps://api.massive.app/v1"]
Portals["Portals"]
Packages["Packages"]
Links["Links"]
Metadata["Metadata"]
Teams["Teams & Users"]
end
subgraph DataPlane["Data Plane — Transfer Infrastructure"]
Agent["MASV Agent"]
Uploader["Web Uploader"]
Storage["Cloud Storage\n(S3-accelerated)"]
end
App -- "configure & orchestrate" --> API
API --- Portals
API --- Packages
API --- Links
API --- Metadata
API --- Teams
API -- "pre-signed URLs &\nblueprints" --> Storage
Agent -- "chunked upload/download" --> Storage
Uploader -- "chunked upload" --> Storage
App -- "transfer files" --> Agent
App -- "transfer files" --> Uploader
```
The architecture diagram shows the separation between the MASV control plane and data plane. The control plane (REST API) handles configuration and orchestration — managing Portals, Packages, Links, Metadata, and Teams. The data plane (MASV Agent and Web Uploader) handles the actual movement of file data through cloud storage using chunked, accelerated transfers. Your application uses the API to define what should happen, while the transfer infrastructure handles how data moves.
A typical MASV workflow follows this pattern:
1. Your application authenticates with an API key associated with a Team user.
2. It creates or manages Team-level resources such as Teamspaces and Portals.
3. External contributors upload through a Portal.
4. Their submission creates a Package containing one or more Files.
5. If the Portal uses a metadata form, the sender submits structured metadata as part of the Package creation flow.
6. Recipients access the Package through download Links, subject to MASV’s authorization model.
### Choosing the right building block
[Section titled “Choosing the right building block”](#choosing-the-right-building-block)
| Concept | Use when you need… |
| ----------------- | -------------------------------------------------- |
| **Team** | A top-level administrative scope |
| **Teamspace** | Project or group-level organization inside a Team |
| **Portal** | A controlled intake surface for external senders |
| **Package** | The actual unit of transfer |
| **Link** | A way for recipients to access downloads |
| **Metadata form** | Structured business context alongside file uploads |
## Next steps
[Section titled “Next steps”](#next-steps)
* **[Authorization](/api/authorization/)** — Choose between API keys and web tokens.
* **[Portals](/api/portals/)** — Build inbound submission workflows.
* **[Uploads](/api/uploads/)** and **[Downloads](/api/downloads/)** — Implement transfer flows.
* **[Teamspaces](/api/teamspaces/)** — Set up project-level access control.
* **[Custom Metadata](/api/custom-metadata/)** — Configure structured intake and routing data.
# Custom metadata
> Create and manage custom metadata forms on MASV Portals to collect structured data with file uploads via the API.
Create and manage custom Metadata forms on your Portals.
Note
[MASV Agent](/agent/automations/) automations do not support Portals with custom metadata forms because they require user interaction to fill out the form.
## Delivery formats
[Section titled “Delivery formats”](#delivery-formats)
Metadata can be delivered in file format and in package upload notification emails. The following delivery formats are supported:
* `json`
* `csv`
* `xml`
* `email_body`
Delivery formats are set on the form and multiple formats can be selected at a time. For file format deliverables, files are placed in the root of the relevant Portal package.
## Custom metadata workflow
[Section titled “Custom metadata workflow”](#custom-metadata-workflow)
1. Team admin creates a form for a Portal.
2. Team admin updates the form to set form fields.
3. User creates a response to this form.
4. User creates a package, providing the form response in the create request.
5. User completes the upload flow.
6. When the user finalizes the package, the metadata is delivered.
## Creating a form
[Section titled “Creating a form”](#creating-a-form)
| Method | Route |
| :----- | :----------------------------- |
| `POST` | `/v1/portals/{portal_id}/form` |
### Headers
[Section titled “Headers”](#headers)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Body
[Section titled “Body”](#body)
| Name | Type | Required | Description |
| ------------------ | --------- | -------- | ---------------------------------------------- |
| `name` | String | Yes | Name of the form |
| `delivery_formats` | String\[] | Yes | Array of delivery formats |
| `form_template_id` | String | No | ID of the form template this form was based on |
| `fields` | Field\[] | No | Optional array of fields |
### Request
[Section titled “Request”](#request)
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -d '{"name": "$FORM_NAME", "delivery_formats": ["csv", "xml"]}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/portals/$PORTAL_ID/form
```
### Response
[Section titled “Response”](#response)
Returns `201 Created` with the form object.
## Form fields
[Section titled “Form fields”](#form-fields)
Form fields have the following properties:
| Property | Type | Description |
| --------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `name` | String | Field name (must be unique per form, not shown to users) |
| `label` | String | Label shown to users |
| `type` | Enum | Input type: `checkbox`, `radio`, `dropdown`, `date`, `short_text`, `long_text`, `number`, `url`, `email`, `package_name`, `package_description`, `sender_email` |
| `visibility` | Enum | Visibility: `required`, `optional`, `readonly`, `hidden` |
| `default_value` | String | Default value |
| `position` | Integer | Render position (ascending order) |
| `options` | String\[] | Options for `checkbox`, `radio`, `dropdown` types |
### Special field types
[Section titled “Special field types”](#special-field-types)
Fields of type `package_name`, `package_description`, and `sender_email` are used in the package creation process. There can only be one field of each type in a form.
## Updating forms
[Section titled “Updating forms”](#updating-forms)
| Method | Route |
| :----- | :----------------------------- |
| `PUT` | `/v1/metadata/forms/{form_id}` |
### Headers
[Section titled “Headers”](#headers-1)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Body
[Section titled “Body”](#body-1)
Pass the full form object including modifications:
| Name | Type | Description |
| ------------------ | ------------ | ----------------------------- |
| `name` | String | The form’s name |
| `delivery_formats` | String\[] | Delivery formats |
| `disabled` | Boolean | If true, the form is disabled |
| `fields` | FormField\[] | Array of form fields |
Caution
When a form’s fields are updated, the old fields are fully replaced.
### Request
[Section titled “Request”](#request-1)
```bash
curl -d '{"name": "$NEW_NAME", "delivery_formats": ["json", "csv"], "disabled": false, "fields": [...]}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X PUT https://api.massive.app/v1/metadata/forms/$FORM_ID
```
## Submitting a form response
[Section titled “Submitting a form response”](#submitting-a-form-response)
When submitting a form response, the only validation performed is on required fields. If a required field is missing a value, the API returns an error.
| Method | Route |
| :----- | :--------------------------------------- |
| `POST` | `/v1/portals/{portal_id}/form/responses` |
### Headers
[Section titled “Headers”](#headers-2)
| Name | Type | Required | Description |
| --------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | \* | [API key](/api/api-keys/) |
| `X-Access-Code` | String | \* | The Portal’s access code |
| `Content-Type` | String | Yes | Must be `application/json` |
Either `X-API-KEY` or `X-Access-Code` is required.
### Body
[Section titled “Body”](#body-2)
A JSON object containing key/value pairs for each field. The key is the field’s `name`, the value is an object with a `value` property:
```json
{
"uploader_name": { "value": "Example uploader name" },
"description": { "value": "Example package description" },
"name": { "value": "Example package name" },
"sender_email": { "value": "user@test.com" }
}
```
### Request
[Section titled “Request”](#request-2)
```bash
curl -d '{"uploader_name": {"value": "Example name"}}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/portals/$PORTAL_ID/form/responses
```
### Response
[Section titled “Response”](#response-1)
Returns `201 Created` with the form response object including its `id`.
## Using form responses
[Section titled “Using form responses”](#using-form-responses)
To attach a form response to a new Portal package, add the form response’s `id` to the create Portal package body under the key `form_data_id`. See [Create a Portal package](/api/uploads/#step-1b--create-a-portal-package).
## Getting package metadata
[Section titled “Getting package metadata”](#getting-package-metadata)
| Method | Route |
| :----- | :----------------------------------- |
| `GET` | `/v1/packages/{package_id}/metadata` |
### Headers
[Section titled “Headers”](#headers-3)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | ---------------------------------- |
| `X-Package-Token` | String | Yes | Full-access package JSON Web Token |
Tip
To get a full-access package token, use the [List Portal Packages](/api/portals/#listing-portal-packages) endpoint with `new=1`, and use the resulting `access_token`.
### Request
[Section titled “Request”](#request-3)
```bash
curl -H "X-Package-Token: $PACKAGE_TOKEN" \
-X GET https://api.massive.app/v1/packages/$PACKAGE_ID/metadata
```
### Response
[Section titled “Response”](#response-2)
Returns `200 OK` with the form response object including all field values.
## Getting Portal metadata fields
[Section titled “Getting Portal metadata fields”](#getting-portal-metadata-fields)
To get the fields required to create a portal form response, use the Get Portal endpoint. If it has a form requirement, an array of form fields is returned under `custom_metadata`.
| Method | Route |
| :----- | :------------------------ |
| `GET` | `/v1/portals/{portal_id}` |
### Headers
[Section titled “Headers”](#headers-4)
| Name | Type | Required | Description |
| --------------- | ------ | -------- | ------------------------- |
| `X-API-KEY` | String | \* | [API key](/api/api-keys/) |
| `X-Access-Code` | String | \* | Portal’s access code |
Either `X-API-KEY` or `X-Access-Code` is required.
## Deleting forms
[Section titled “Deleting forms”](#deleting-forms)
| Method | Route |
| :------- | :----------------------------- |
| `DELETE` | `/v1/metadata/forms/{form_id}` |
### Request
[Section titled “Request”](#request-4)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X DELETE https://api.massive.app/v1/metadata/forms/$FORM_ID
```
### Response
[Section titled “Response”](#response-3)
Returns `204 No Content`.
## Creating a form template
[Section titled “Creating a form template”](#creating-a-form-template)
| Method | Route |
| :----- | :------------------------------------ |
| `POST` | `/v1/teams/{team_id}/forms/templates` |
### Body
[Section titled “Body”](#body-3)
| Name | Type | Required | Description |
| ------------------ | --------- | -------- | ------------------------- |
| `name` | String | Yes | Name of the form template |
| `delivery_formats` | String\[] | Yes | Array of delivery formats |
### Request
[Section titled “Request”](#request-5)
```bash
curl -d '{"name": "$FORM_TEMPLATE_NAME", "delivery_formats": ["csv", "xml"]}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/teams/$TEAM_ID/forms/templates
```
## Updating form templates
[Section titled “Updating form templates”](#updating-form-templates)
| Method | Route |
| :----- | :------------------------------------------------ |
| `PUT` | `/v1/metadata/forms/templates/{form_template_id}` |
Same body format as updating forms.
## Deleting form templates
[Section titled “Deleting form templates”](#deleting-form-templates)
| Method | Route |
| :------- | :------------------------------------------------ |
| `DELETE` | `/v1/metadata/forms/templates/{form_template_id}` |
### Request
[Section titled “Request”](#request-6)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X DELETE https://api.massive.app/v1/metadata/forms/templates/$FORM_TEMPLATE_ID
```
### Response
[Section titled “Response”](#response-4)
Returns `204 No Content`.
# Download files with the MASV API
> Download files from MASV packages using the API — obtain link info, list files, get download URLs, and retrieve files.
Download files directly from MASV’s private cloud infrastructure.
MASV makes the following downloads available:
* Each individual file that was uploaded as part of the package.
* A zip file for Microsoft Windows and Linux platforms.
* A zip file for macOS platforms.
Diagram source
```plaintext
sequenceDiagram
participant Client
participant API as MASV API
participant Storage as Cloud Storage
Client->>API: Get link info (link ID + secret)
API-->>Client: Package ID + package token
Client->>API: List files in Package
API-->>Client: File list (IDs, names, sizes)
loop For each file
Client->>API: Get download URL (file ID)
API-->>Client: Pre-signed download URL
Client->>Storage: Download file (GET)
Storage-->>Client: File data
end
```
The download lifecycle sequence: the client obtains link information (Package ID and token) from the API using a link ID and secret. It then lists the files in the Package. For each file, the client requests a pre-signed download URL from the API and downloads the file directly from cloud storage.
## Step 1: Obtain package link information
[Section titled “Step 1: Obtain package link information”](#step-1-obtain-package-link-information)
The package link information can be obtained from the recipient’s email. The package information requires the `link_id` and `secret`. See the [Links](/api/links/#creating-links) page for more details.
| Method | Route |
| :----- | :----------------- |
| `GET` | `/links/{link_id}` |
### Headers
[Section titled “Headers”](#headers)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | ---------------------------------------------------- |
| `X-Link-Password` | String | No | Sender-supplied password associated with the package |
### URL parameters
[Section titled “URL parameters”](#url-parameters)
| Name | Type | Required | Description |
| -------- | ------ | -------- | ---------------------------------------------- |
| `secret` | String | Yes | Secret token associated with the download link |
### Request
[Section titled “Request”](#request)
```bash
curl -H "X-Link-Password: $PACKAGE_PASSWORD" \
-X GET "https://api.massive.app/v1/links/$LINK_ID?secret=$SECRET"
```
### Response
[Section titled “Response”](#response)
After a successful request, this endpoint returns `200 OK`:
```json
{
"branding": {
"primary_color": "#1DD4CA"
},
"expiry": "2019-02-13T21:33:50.944Z",
"id": "01D2TMG9F5GHMHX8RWZ453VA35",
"name": "MASV Package",
"package_id": "01D2TMG9F0J6JD5SJ55NY27MXJ",
"package_size": 28176354,
"package_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"recipient_email": "someoneelse@masv.io",
"sender_email": "someone@masv.io"
}
```
Note
The `package_id` and `package_token` are required to list the files associated with the package. Requests for packages containing greater than 10,000 files are rate limited to once every 5 seconds.
## Step 2: Obtain file listing for the package
[Section titled “Step 2: Obtain file listing for the package”](#step-2-obtain-file-listing-for-the-package)
| Method | Route |
| :----- | :----------------------------- |
| `GET` | `/packages/{package_id}/files` |
### Headers
[Section titled “Headers”](#headers-1)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | ----------------------------- |
| `X-Package-Token` | String | Yes | Package [token](/api/tokens/) |
### URL parameters
[Section titled “URL parameters”](#url-parameters-1)
| Name | Type | Required | Description |
| ------------ | ------ | -------- | ------------------------------- |
| `package_id` | String | Yes | Package ID obtained from step 1 |
### Request
[Section titled “Request”](#request-1)
```bash
curl -H "X-Package-Token: $PACKAGE_TOKEN" \
-X GET "https://api.massive.app/v1/packages/$PACKAGE_ID/files"
```
### Response
[Section titled “Response”](#response-1)
After a successful request, this endpoint returns `200 OK`:
```json
[
{
"id": "01D2TMGA09VHKVW6G3MJH2EP57",
"kind": "file",
"last_modified": "0001-01-01T00:00:00.000Z",
"name": "Hawaii_4_27Retina_L.jpg",
"size": 14914472,
"virus_detected": false
},
{
"id": "01D2TMGZMRZNDWD3852NNASF4D",
"kind": "zip_windows",
"last_modified": "2019-02-03T21:34:13.655Z",
"name": "windows.zip",
"virus_detected": false
},
{
"id": "01D2TMGZMSGYSXFB7MV5V9ZM0X",
"kind": "zip_mac",
"last_modified": "2019-02-03T21:34:13.657Z",
"name": "mac.zip",
"virus_detected": false
}
]
```
Note
Files with `virus_detected: true` indicate that a virus has been detected and the file cannot be downloaded. A `403` status code is returned if an attempt is made to download it.
## Step 3: Obtain download links for the files
[Section titled “Step 3: Obtain download links for the files”](#step-3-obtain-download-links-for-the-files)
Direct file download URLs are short-lived and not intended for sharing or publishing. They are designed to be used immediately following the request. Use the package download URL (`https://get.massive.io/{link_id}?secret={secret}`) for sharing, which is active until the package expires or is deleted.
If you are building a custom download page, call the MASV API to get the download URL on demand when the user starts the download. You can include an optional `expiry` parameter to request a specific time for the URL to become inactive, but this cannot exceed the earliest of the authenticated link expiry time or 7 days.
| Method | Route |
| :----- | :------------------------------------------------ |
| `GET` | `/packages/{package_id}/files/{file_id}/download` |
### Headers
[Section titled “Headers”](#headers-2)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | ----------------------------- |
| `X-Package-Token` | String | Yes | Package [token](/api/tokens/) |
### URL parameters
[Section titled “URL parameters”](#url-parameters-2)
| Name | Type | Required | Description |
| ------------ | --------- | -------- | ------------------------------------------------------------------------------------------------------------ |
| `package_id` | String | Yes | Package ID obtained from step 1 |
| `file_id` | String | Yes | File ID obtained from step 2 |
| `expiry` | Date-time | No | Optional custom expiry time for the download URL. Format: [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) |
### Request
[Section titled “Request”](#request-2)
```bash
curl -H "X-Package-Token: $PACKAGE_TOKEN" \
-X GET "https://api.massive.app/v1/packages/$PACKAGE_ID/files/$FILE_ID/download"
```
### Response
[Section titled “Response”](#response-2)
After a successful request, this endpoint returns `200 OK`:
```json
{
"method": "GET",
"url": "https://masv3-storage-stag-wdc.s3-accelerate.amazonaws.com/..."
}
```
## Step 4: Download the file
[Section titled “Step 4: Download the file”](#step-4-download-the-file)
Note
Direct file URLs should not be shared or published. See step 3 above for details.
Use the `method` and `url` from the previous step to download the file:
```bash
curl -X $METHOD "$URL" > /path/to/target/file
```
# Filters
> Create and manage MASV filters to route files within packages to specific Portal cloud connections based on rules.
Note
Filters are a Beta feature. Please contact for information about enabling filters for your Team.
Route files within a Package to specific Portal cloud connections by creating and applying filters.
A filter is a named collection of filter rules used to determine which files should be transferred to the associated connection. This allows you to ingest packages with a single [MASV Portal](/api/portals/) and transfer only specific files to the connections you want. For example, for a package that includes three different file types, you can apply a MASV filter to route each file type to a different connection.
A filter combines all rules into a single expression using the AND operation. A filter must have at least one rule. The maximum number of rules per filter is 20. Each rule in the filter must reference a unique criterion. For example, you cannot have two rules in the same filter with a criterion set to `file_name`.
## Filter rules
[Section titled “Filter rules”](#filter-rules)
A filter rule is composed of the following components:
* An action
* A criterion
* An operator
* Value(s)
The basic structure of a filter rule is: *“action files when/where criterion operator values”*. For example, *“include files where file name regex matches `\.(?i)(jpe?g|png|gif)$`”*.
| Name | Type | Required | Description |
| ----------- | --------- | -------- | --------------------------------- |
| `action` | String | Yes | The action for the filter rule |
| `criterion` | String | Yes | The criterion for the filter rule |
| `operator` | String | Yes | The operator for the filter rule |
| `values` | String\[] | Yes | The values for the filter rule |
### Filter rule actions
[Section titled “Filter rule actions”](#filter-rule-actions)
| Action | Description |
| :-------- | :-------------------------------------------------------- |
| `include` | Includes any files that match this rule in the transfer |
| `exclude` | Excludes any files that match this rule from the transfer |
### Filter rule criteria
[Section titled “Filter rule criteria”](#filter-rule-criteria)
| Criterion | Description |
| :---------- | :-------------------------------------------------------------- |
| `file_name` | Extracts the file name from the file for comparison in the rule |
### Filter rule operators
[Section titled “Filter rule operators”](#filter-rule-operators)
| Operator | Description |
| :------------ | :---------------------------------------------------------------------------------- |
| `regex_match` | Evaluates to true if the criterion regex matches the pattern specified by the value |
Note
The type of regex used by the `regex_match` operator is [RE2](https://github.com/google/re2/wiki/Syntax).
### Filter rule values
[Section titled “Filter rule values”](#filter-rule-values)
A value refers to the input specified for a rule and uses a pattern determined by the type of operator and criterion. Values are constrained by the following requirements:
* If the `file_name` criterion is selected and the operator is `regex_match`, the value must be a valid regex string.
## Creating a filter
[Section titled “Creating a filter”](#creating-a-filter)
| Method | Route |
| :----- | :---------------------------- |
| `POST` | `/v1/teams/{team_id}/filters` |
### Headers
[Section titled “Headers”](#headers)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Body
[Section titled “Body”](#body)
| Name | Type | Required | Description |
| ------- | ------------- | -------- | ------------------------------------------------------ |
| `name` | String | Yes | Name of the filter |
| `rules` | FilterRule\[] | Yes | An array of filter rules to associate with this filter |
### Request
[Section titled “Request”](#request)
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -d '{"name": "$FILTER_NAME", "rules": [$FILTER_RULES]}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/teams/$TEAM_ID/filters
```
### Response
[Section titled “Response”](#response)
After a successful request, this endpoint returns `201 Created`:
```json
{
"name": "Test Filter",
"id": "01FV2TB1KHRFQJ76MEWPKDX2G9",
"created_at": "2025-10-07T19:07:09.101Z",
"updated_at": "2025-10-07T19:07:09.101Z",
"rules": [
{
"action": "include",
"criterion": "file_name",
"operator": "regex_match",
"values": ["\\.(?i)(jpe?g|png|gif)$"]
}
]
}
```
## Getting a filter
[Section titled “Getting a filter”](#getting-a-filter)
| Method | Route |
| :----- | :------------------------ |
| `GET` | `/v1/filters/{filter_id}` |
### Headers
[Section titled “Headers”](#headers-1)
| Name | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
### Request
[Section titled “Request”](#request-1)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X GET https://api.massive.app/v1/filters/$FILTER_ID
```
### Response
[Section titled “Response”](#response-1)
Returns `200 OK` with the filter object including its rules.
## Updating a filter
[Section titled “Updating a filter”](#updating-a-filter)
Note
Updating a filter will replace all existing rules with the new set of rules provided.
| Method | Route |
| :----- | :------------------------ |
| `PUT` | `/v1/filters/{filter_id}` |
### Headers
[Section titled “Headers”](#headers-2)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Body
[Section titled “Body”](#body-1)
| Name | Type | Required | Description |
| ------- | ------------- | -------- | ---------------------------------------------------- |
| `name` | String | No | Updated name of the filter |
| `rules` | FilterRule\[] | Yes | An array of filter rules to overwrite on this filter |
### Request
[Section titled “Request”](#request-2)
```bash
curl -d '{"name": "$UPDATED_FILTER_NAME", "rules": [$UPDATED_FILTER_RULES]}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X PUT https://api.massive.app/v1/filters/$FILTER_ID
```
### Response
[Section titled “Response”](#response-2)
Returns `200 OK` with the updated filter object.
## Deleting a filter
[Section titled “Deleting a filter”](#deleting-a-filter)
| Method | Route |
| :------- | :------------------------ |
| `DELETE` | `/v1/filters/{filter_id}` |
### Headers
[Section titled “Headers”](#headers-3)
| Name | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
### Request
[Section titled “Request”](#request-3)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X DELETE https://api.massive.app/v1/filters/$FILTER_ID
```
### Response
[Section titled “Response”](#response-3)
Returns `204 No Content`.
## Adding a filter to a Portal connection
[Section titled “Adding a filter to a Portal connection”](#adding-a-filter-to-a-portal-connection)
To apply a filter to a Portal connection, provide the `filter_id` when [updating the Portal](/api/portals/#updating-a-portal).
Note
Filters cannot be applied to [storage gateway](/agent/storage-gateway/) connections. All other connections are supported.
## Removing a filter from a Portal connection
[Section titled “Removing a filter from a Portal connection”](#removing-a-filter-from-a-portal-connection)
To remove a filter from a Portal connection, [update the Portal](/api/portals/#updating-a-portal) and omit the `filter_id`.
# Getting started with the MASV API
> Authenticate, understand the system model, and create your first file transfer using the MASV REST API.
The MASV API provides a programmatic interface for managing large file workflows. Use it to create and manage Portals, initiate and track file transfers, apply metadata, and orchestrate how files move through your organization and into downstream systems.
Choosing the right tool
We recommend that you review the functionality offered by the [MASV Agent](/agent/) and browser-based [Web Uploader](/web-sdks/uploader) and [Web Downloader](/web-sdks/downloader) before diving into the MASV API. These tools are designed to optimize file transfers out-of-the-box. Use the API when you need fine-grained control or your platform isn’t supported by the MASV Agent.
This guide walks you through the essentials: authenticating your requests, understanding the system model, and executing a typical transfer workflow.
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
Before you begin, make sure you have:
* A MASV account with **Owner**, **Admin**, or [Custom](https://help.massive.io/en/roles-and-permissions-in-masv#custom-roles) role with API permissions.
* An API key (see [API Keys](/api/api-keys/) for creation steps)
* A tool for making HTTP requests (`curl`, Postman, or your language’s HTTP client)
## API basics
[Section titled “API basics”](#api-basics)
The MASV API is a RESTful service at `https://api.massive.app/v1/`.
All connections require TLS 1.2 or 1.3. Requests use standard HTTP methods:
| Method | Action |
| -------- | ---------------------- |
| `GET` | Read, list, or search |
| `POST` | Create or authenticate |
| `PUT` | Update |
| `DELETE` | Delete |
Every `POST` and `PUT` request must include a `Content-Type: application/json` header.
List endpoints use page-based pagination (`page` and `limit` query parameters). The API also enforces rate limits — if you exceed them, you’ll receive a `429` response with a `Retry-After` header.
Note
The version prefix (`v1`) can vary by endpoint. Check the route information for individual endpoints in the [API reference pages](/api/).
## Authentication
[Section titled “Authentication”](#authentication)
The MASV API uses **API keys** as the primary authentication mechanism. An API key is tied to a specific user and inherits that user’s role-based permissions.
Include your API key in the `X-API-KEY` header on every request:
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -X GET "https://api.massive.app/v1/teams" \
-H "X-API-KEY: $MASV_API_KEY"
```
For operations like uploading or downloading files, MASV also uses **scoped web tokens**. These are short-lived, limited-scope tokens suitable for client-side or temporary workflows. See [Package & Transfer Tokens](/api/tokens/) for details.
Tip
API keys are returned only once at creation time. Store them securely — you cannot retrieve the key value later. Rotate your API keys at least every 90 days. If you suspect a key has been compromised, rotate it immediately and review your Team’s recent API activity.
For a full overview of authorization options, see [Authorization](/api/authorization/).
## System model overview
[Section titled “System model overview”](#system-model-overview)
MASV is built around a few core objects:
* **Team** — The top-level organizational boundary. Contains users, Portals, Teamspaces, and Packages.
* **Portal** — A controlled ingestion point where users or external contributors upload files.
* **Package** — The unit of transfer. Contains files plus metadata and delivery configuration.
* **Link** — A shareable reference that gives recipients download access to a Package.
A typical flow: files are uploaded through a Portal, creating a Package. That Package is then shared via Links or routed to storage and downstream systems.
Diagram source
```plaintext
graph LR
Team --> Portal
Team --> Package
Portal --> Package
Package --> Link
```
The MASV system model: a Team contains Portals and Packages. A Portal creates Packages when files are uploaded through it. A Package produces Links that give recipients download access.
For a deeper look at Teams, Users, Roles, Teamspaces, Metadata, and how these objects relate, see [Core Concepts](/api/core-concepts/).
## Control plane vs. data plane
[Section titled “Control plane vs. data plane”](#control-plane-vs-data-plane)
MASV separates responsibilities between two layers:
* **Control plane (API)** — Manages configuration and orchestration: creating Portals, defining Packages, tracking transfer state, managing users.
* **Data plane (Agent / Uploader)** — Handles the actual movement of files: chunking, acceleration, retries, and delivery.
Your application uses the API to define *what* should happen. MASV’s transfer infrastructure handles *how* the data moves. This means you don’t need to build your own file transfer mechanisms.
## Creating your first transfer
[Section titled “Creating your first transfer”](#creating-your-first-transfer)
### Upload lifecycle
[Section titled “Upload lifecycle”](#upload-lifecycle)
The following diagram shows the full upload lifecycle when using the MASV API directly. Each file goes through a create → chunk → finalize cycle before the Package itself is finalized.
Diagram source
```plaintext
sequenceDiagram
participant Client
participant API as MASV API
participant Storage as Cloud Storage
Client->>API: Create Package
API-->>Client: Package ID + access token
loop For each file
Client->>API: Add file to Package
API-->>Client: Create blueprint
Client->>Storage: Create file in cloud storage (blueprint)
Storage-->>Client: Upload ID
Client->>API: Obtain upload URLs (chunk count)
API-->>Client: Pre-signed URLs (blueprints)
loop For each chunk
Client->>Storage: Upload chunk (PUT)
Storage-->>Client: ETag
end
Client->>API: Finalize file (ETags + upload ID)
API-->>Client: File finalized
end
Client->>API: Finalize Package
API-->>Client: Package finalized — delivery triggered
```
The upload lifecycle sequence: the client creates a Package via the API and receives an access token. For each file, the client adds the file to the Package, creates it in cloud storage using a blueprint, obtains pre-signed upload URLs for each chunk, uploads the chunks directly to cloud storage, then finalizes the file. After all files are uploaded, the client finalizes the Package to trigger delivery.
### Download lifecycle
[Section titled “Download lifecycle”](#download-lifecycle)
Downloading follows a simpler flow. The client resolves a Link, lists the available files, obtains download URLs, and downloads each file directly from cloud storage.
Diagram source
```plaintext
sequenceDiagram
participant Client
participant API as MASV API
participant Storage as Cloud Storage
Client->>API: Get link info (link ID + secret)
API-->>Client: Package ID + package token
Client->>API: List files in Package
API-->>Client: File list (IDs, names, sizes)
loop For each file
Client->>API: Get download URL (file ID)
API-->>Client: Pre-signed download URL
Client->>Storage: Download file (GET)
Storage-->>Client: File data
end
```
The download lifecycle sequence: the client obtains link information (Package ID and token) from the API using a link ID and secret. It then lists the files in the Package. For each file, the client requests a pre-signed download URL from the API and downloads the file directly from cloud storage.
A typical API-driven transfer follows these steps:
### Step 1: Get your Team ID
[Section titled “Step 1: Get your Team ID”](#step-1-get-your-team-id)
Most API operations are scoped to a Team. Retrieve your Team ID first:
```bash
curl -X GET "https://api.massive.app/v1/teams" \
-H "X-API-KEY: $MASV_API_KEY"
```
The response includes your Team’s `id` field.
### Step 2: Create a Package
[Section titled “Step 2: Create a Package”](#step-2-create-a-package)
Create a Package to define the transfer. Include the Team ID and any metadata:
```bash
curl -X POST "https://api.massive.app/v1/teams/{team_id}/packages" \
-H "X-API-KEY: $MASV_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "My First Package",
"description": "Test transfer via API",
"recipients": ["recipient@example.com"]
}'
```
The response returns the Package `id` you’ll use in subsequent steps.
### Step 3: Upload files
[Section titled “Step 3: Upload files”](#step-3-upload-files)
Add a file to the Package, then upload its contents in chunks using pre-signed URLs:
```bash
# Add a file to the Package
curl -X POST "https://api.massive.app/v1/packages/{package_id}/files" \
-H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"kind": "file",
"name": "video.mp4",
"path": "",
"size": 104857600,
"last_modified": "2026-01-15T10:00:00Z"
}'
```
The response includes a `create_blueprint` for initializing the file in cloud storage and the `file` ID. Use the blueprint to create the file in storage, then request pre-signed URLs for each chunk:
```bash
# Obtain upload URLs for chunks (start=0, count=number_of_chunks)
curl -X POST "https://api.massive.app/v1/packages/{package_id}/files/{file_id}?start=0&count=2" \
-H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json"
```
Upload each chunk to its pre-signed URL with a `PUT` request, then finalize the file by submitting the ETags returned from each chunk upload.
For the complete chunking workflow (blueprint usage, chunk sizing, and file finalization), see [Uploads](/api/uploads/).
### Step 4: Finalize the Package
[Section titled “Step 4: Finalize the Package”](#step-4-finalize-the-package)
After all files are uploaded, finalize the package to make it available for delivery:
```bash
curl -X POST "https://api.massive.app/v1/teams/{team_id}/packages/{package_id}/finalize" \
-H "X-API-KEY: $MASV_API_KEY" \
-H "Content-Type: application/json"
```
After finalization, recipients receive download access and any configured integrations (webhooks, cloud connections) are triggered.
## Error handling
[Section titled “Error handling”](#error-handling)
The API uses standard HTTP status codes:
| Range | Meaning |
| ----- | ------------------------------------------ |
| 2xx | Success |
| 4xx | Client error (invalid input, auth failure) |
| 5xx | Server error (retryable) |
For production integrations, implement retry logic with exponential backoff for 5xx responses, and log all API interactions for diagnostics.
## Next steps
[Section titled “Next steps”](#next-steps)
* **[Core Concepts](/api/core-concepts/)** — Understand Teams, Portals, Packages, Links, Teamspaces, and Metadata in depth.
* **[Authorization](/api/authorization/)** — Explore API key and web token authentication patterns.
* **[Uploads](/api/uploads/)** — Learn the full upload lifecycle.
* **[Downloads](/api/downloads/)** — Learn how to download Packages and files.
* **[Portals](/api/portals/)** — Set up controlled ingestion points for external contributors.
* **[Webhooks](/api/webhooks/)** — Receive event notifications for automation workflows.
# Links
> Create, list, and disable download links for MASV packages using the API with support for passwords and access limits.
Manage download Links for your Packages using the MASV API.
## Creating links
[Section titled “Creating links”](#creating-links)
You can create additional direct-download links or send a link to a specific email recipient after the upload has been finalized. To create a link, the package must be in the `new` or `finalized` state. Links created while the upload is in progress will remain inactive until the package is finalized.
| Method | Route |
| :----- | :----------------------------- |
| `POST` | `/packages/{package_id}/links` |
### Headers
[Section titled “Headers”](#headers)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | -------------------------- |
| `X-Package-Token` | String | Yes | Package JSON Web Token |
| `Content-Type` | String | Yes | Must be `application/json` |
### Body
[Section titled “Body”](#body)
| Name | Type | Required | Description |
| ---------- | ------ | -------- | -------------------------------------------------------------------------------------- |
| `email` | String | No | Recipient email address or empty for direct-download link |
| `password` | String | No | Password that user must enter before being able to view and download the package files |
### Request
[Section titled “Request”](#request)
```bash
curl -d '{"email":"$EMAIL", "password":"$PASSWORD"}' \
-H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/packages/$PACKAGE_ID/links
```
### Response
[Section titled “Response”](#response)
After a successful request, this endpoint returns `201 Created`:
```json
{
"access_limit": 5,
"access_limit_enabled": true,
"active": true,
"download_secret": "bqkBtnPwvmkgJpHN",
"email": "someone@someproductionhouse.com",
"expiry": "2020-12-04T13:14:01.038Z",
"id": "01EKX32ZSAPSCFC97W5Z423QKY",
"password": "****"
}
```
| Property | Description |
| ---------------------- | ------------------------------------------------------------------------------------ |
| `access_limit` | Number of times a download can be initiated using this link before it becomes locked |
| `access_limit_enabled` | Whether an `access_limit` is enforced |
| `active` | Whether the link can be used to download the package |
| `id` | Unique identifier for the link record |
| `download_secret` | A key provided with `id` to request a token to download the file |
| `email` | Recipient’s email (defaults to uploader email if not provided) |
| `password` | Indicates if a password is attached to the link. `****` means a password is required |
Note
Take note of the `id` and `download_secret` as they are required to access the download page. The `download_secret` is only returned in this response if the `email` is empty and cannot be retrieved via the API again.
After the link has been created, MASV sends an email to the recipient automatically (if provided) and the link can be used to access the package via: `https://get.massive.io/{link_id}?secret={download_secret}`
Note
Repeat the request for each additional email recipient or direct-download link. Only one recipient per link is permitted.
## List package links
[Section titled “List package links”](#list-package-links)
You can retrieve a list of all links that belong to a package.
| Method | Route |
| :----- | :----------------------------- |
| `GET` | `/packages/{package_id}/links` |
### Headers
[Section titled “Headers”](#headers-1)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | -------------------------- |
| `X-Package-Token` | String | Yes | Package JSON Web Token |
| `Content-Type` | String | Yes | Must be `application/json` |
### URL parameters
[Section titled “URL parameters”](#url-parameters)
| Name | Type | Required | Description |
| ------------ | ------ | -------- | ----------------------------------------------------- |
| `package_id` | String | Yes | The package ID returned during create package request |
### Request
[Section titled “Request”](#request-1)
```bash
curl -H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-X GET https://api.massive.app/v1/packages/$PACKAGE_ID/links
```
### Response
[Section titled “Response”](#response-1)
This endpoint returns `200 OK` with an array of link objects.
## Disable a link
[Section titled “Disable a link”](#disable-a-link)
You can disable a download link to prevent new downloads and remove access to package details from the download page. The link record will remain for historical purposes.
| Method | Route |
| :------- | :--------------------------------------- |
| `DELETE` | `/packages/{package_id}/links/{link_id}` |
### Headers
[Section titled “Headers”](#headers-2)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | -------------------------- |
| `X-Package-Token` | String | Yes | Package JSON Web Token |
| `Content-Type` | String | Yes | Must be `application/json` |
### URL parameters
[Section titled “URL parameters”](#url-parameters-1)
| Name | Type | Required | Description |
| ------------ | ------ | -------- | ----------------------------------------------- |
| `package_id` | String | Yes | The package ID |
| `link_id` | String | Yes | The link ID returned during create link request |
### Request
[Section titled “Request”](#request-2)
```bash
curl -H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-X DELETE https://api.massive.app/v1/packages/$PACKAGE_ID/links/$LINK_ID
```
### Response
[Section titled “Response”](#response-2)
This endpoint returns `204 No Content` with an empty body. Any downloads in progress at the time of deletion may complete normally, but the download page will become inaccessible and new downloads cannot be started.
# Packages
> List, rename, update expiry, delete, and archive MASV packages using the API with full package object reference.
Manage Packages that have been sent and received under your account using the MASV API.
Tip
Packages tied to Portals are discussed in detail in [Portals](/api/portals/), but can be managed the same as a sent Package.
## Listing sent Packages
[Section titled “Listing sent Packages”](#listing-sent-packages)
You can retrieve a list of Packages in a Team that meet specific criteria, subject to the MASV [access policy](https://help.massive.io/en/what-are-the-permissions-for-owner-admins-and-members-of-your-team).
This endpoint returns a list of Packages that were sent to a Team. To retrieve a list of Packages received from a Portal, see [Listing Portal packages](/api/portals/#listing-portal-packages).
| Method | Route |
| :----- | :------------------------------- |
| `GET` | `/v1.1/teams/{team_id}/packages` |
### Headers
[Section titled “Headers”](#headers)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Query parameters
[Section titled “Query parameters”](#query-parameters)
| Name | Type | Required | Description |
| ------------------ | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `page` | Integer | Yes | Page number. Default: `1` |
| `limit` | Integer | Yes | Records per page, 1–50. Default: `50` |
| `sort` | String | No | Sort ascending (`fieldname`) or descending (`-fieldname`). Accepted: `name`, `created_at`, `size`, `usage_total_bytes`, `expiry`, `sender`, `status`, `teamspace_id` |
| `status` | String\[] | No | Filter by state: `new`, `finalized`, `expired`, `archived`. Default: `[finalized, expired]` |
| `name` | String | No | Substring match on package name |
| `sender` | String | No | Match sender email |
| `tags` | String | No | Comma-separated [tag](/api/tags/) IDs |
| `created_at_start` | String | No | Created after date. Format: [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) |
| `created_at_end` | String | No | Created before date. Format: [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) |
| `teamspaces` | String | No | Comma-separated [Teamspace](/api/teamspaces/) IDs |
| `expiry_start` | String | No | Expires after date |
| `expiry_end` | String | No | Expires before date |
| `extra_storage` | Boolean | No | Filter by extra storage usage |
### Path parameters
[Section titled “Path parameters”](#path-parameters)
| Name | Type | Required | Description |
| --------- | ------ | -------- | ----------- |
| `team_id` | String | Yes | The Team ID |
### Request
[Section titled “Request”](#request)
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -H "Content-Type: application/json" \
-H "X-API-KEY: $API_KEY" \
-X GET "https://api.massive.app/v1.1/teams/$TEAM_ID/packages?page=1&limit=3"
```
### Response
[Section titled “Response”](#response)
Returns `200 OK`:
```json
{
"metadata": {
"total": 34
},
"records": [
{
"access_limit": 3,
"access_token": "...",
"contains_virus": false,
"created_at": "2022-11-01T20:09:48.249Z",
"description": "Good day to you",
"expiry": "2022-11-06T20:09:48.175Z",
"id": "E1XGADMXPSLEE9NGH7WRWJ069D",
"name": "Delta's Team - 202211012009",
"sender": "gretta@example.com",
"size": 41610,
"state": "expired",
"total_files": 1
}
]
}
```
| Property | Description |
| ---------- | -------------------------------------------------------- |
| `metadata` | Contains `total` — the total number of matching Packages |
| `records` | Array of up to `limit` Package objects |
## Changing the name
[Section titled “Changing the name”](#changing-the-name)
| Method | Route |
| :----- | :-------------------------- |
| `PUT` | `/v1/packages/{package_id}` |
### Headers
[Section titled “Headers”](#headers-1)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | ------------------------------------ |
| `X-Package-Token` | String | Yes | Package [access token](/api/tokens/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Body
[Section titled “Body”](#body)
| Name | Type | Required | Description |
| ------ | ------ | -------- | -------------------- |
| `name` | String | Yes | Name for the Package |
### Request
[Section titled “Request”](#request-1)
```bash
curl -d '{"name": "$NAME"}' \
-H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-X PUT https://api.massive.app/v1/packages/$PACKAGE_ID
```
### Response
[Section titled “Response”](#response-1)
Returns `200 OK` with the updated Package object.
## Changing the expiry
[Section titled “Changing the expiry”](#changing-the-expiry)
MASV stores an uploaded Package until its expiry time. When it expires, MASV deletes the Package. Expired Packages cannot be [downloaded](/api/downloads/) and [links](/api/links/) do not give access to the Package.
Caution
Extended Storage charges are incurred beyond the free period included with the team’s [pricing plan](https://masv.io/pricing/). For details, see [How does Extended Storage Work](https://help.massive.io/en/how-does-extended-storage-work).
| Method | Route |
| :----- | :--------------------------------- |
| `PUT` | `/v1/packages/{package_id}/expiry` |
### Headers
[Section titled “Headers”](#headers-2)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | ------------------------------------ |
| `X-Package-Token` | String | Yes | Package [access token](/api/tokens/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Body
[Section titled “Body”](#body-1)
| Name | Type | Required | Description |
| ------------------- | ------- | -------- | ---------------------------------------------------------------------------- |
| `expiry` | String | Yes | Date-time expiry. Format: [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) |
| `unlimited_storage` | Boolean | No | If `true`, omit `expiry` from the request. Default: `false` |
### Request
[Section titled “Request”](#request-2)
```bash
curl -d '{"expiry": "$EXPIRY"}' \
-H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-X PUT https://api.massive.app/v1/packages/$PACKAGE_ID/expiry
```
### Response
[Section titled “Response”](#response-2)
Returns `200 OK` with the updated Package object.
## Deleting a Package
[Section titled “Deleting a Package”](#deleting-a-package)
Caution
Deleting a Package cannot be undone.
When MASV deletes a Package, it deletes the Package’s files and [custom metadata](/api/custom-metadata/) from MASV storage. It keeps the Package’s name, recipient list, and related information, and sets the Package’s state to `expired`.
| Method | Route |
| :------- | :-------------------------- |
| `DELETE` | `/v1/packages/{package_id}` |
### Headers
[Section titled “Headers”](#headers-3)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | ------------------------------------ |
| `X-Package-Token` | String | Yes | Package [access token](/api/tokens/) |
### Request
[Section titled “Request”](#request-3)
```bash
curl -H "X-Package-Token: $PACKAGE_TOKEN" \
-X DELETE https://api.massive.app/v1/packages/$PACKAGE_ID
```
### Response
[Section titled “Response”](#response-3)
Returns `204 No Content`.
## Archiving expired Packages
[Section titled “Archiving expired Packages”](#archiving-expired-packages)
Authorized users can archive or unarchive any Package that was previously in an `expired` state. The `unarchive` endpoint puts the Package back to an `expired` state.
| Method | Route |
| :----- | :------------------------------------ |
| `PUT` | `/v1/packages/{package_id}/archive` |
| `PUT` | `/v1/packages/{package_id}/unarchive` |
### Request
[Section titled “Request”](#request-4)
```bash
curl -H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-X PUT https://api.massive.app/v1/packages/$PACKAGE_ID/archive
```
### Response
[Section titled “Response”](#response-4)
Returns `204 No Content`.
## Archiving sent Packages (bulk)
[Section titled “Archiving sent Packages (bulk)”](#archiving-sent-packages-bulk)
Archive or unarchive multiple expired Packages sent by the Team within a date range.
| Method | Route |
| :----- | :------------------------------ |
| `PUT` | `/v1/teams/{team_id}/archive` |
| `PUT` | `/v1/teams/{team_id}/unarchive` |
### Body
[Section titled “Body”](#body-2)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | --------------------------------- |
| `expiry_start` | String | Yes | Earliest Package expiry date-time |
| `expiry_end` | String | Yes | Latest Package expiry date-time |
### Request
[Section titled “Request”](#request-5)
```bash
curl -d '{"expiry_start": "$EXPIRY_START", "expiry_end": "$EXPIRY_END"}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X PUT https://api.massive.app/v1/teams/$TEAM_ID/archive
```
### Response
[Section titled “Response”](#response-5)
Returns `200 OK`:
```json
{
"total_updated": 51
}
```
## Archiving received Packages (bulk)
[Section titled “Archiving received Packages (bulk)”](#archiving-received-packages-bulk)
Archive or unarchive multiple expired Packages received through a Portal.
| Method | Route |
| :----- | :------------------------------------ |
| `PUT` | `/v1/teams/{team_id}/inbox/archive` |
| `PUT` | `/v1/teams/{team_id}/inbox/unarchive` |
Same body and response format as archiving sent Packages above.
## Package object
[Section titled “Package object”](#package-object)
| Property | Type | Description |
| -------------------- | --------- | --------------------------------------------------------- |
| `access_limit` | Integer | Default access limit for new links |
| `access_token` | String | Access token for managing the Package |
| `contains_virus` | Boolean | Whether the Package contains a virus |
| `created_at` | String | Creation date-time (ISO 8601) |
| `custom_expiry` | Boolean | Whether a custom expiry was set |
| `custom_metadata_id` | String | ID of the associated form response |
| `description` | String | Package description |
| `expiry` | String | Expiry date-time (ISO 8601) |
| `id` | String | Unique Package ID |
| `name` | String | Package name |
| `password` | String | Default password for new links (empty if none) |
| `recipients` | String\[] | Recipient email addresses |
| `sender` | String | Sender’s email address |
| `size` | Integer | Package size in bytes |
| `state` | String | Status: `new`, `finalized`, `expired`, or `archived` |
| `teamspace` | Object | The Teamspace the Package belongs to |
| `total_files` | Integer | Number of files in the Package |
| `unlimited_storage` | Boolean | Whether unlimited storage is enabled |
| `updated_at` | String | Last modified date-time |
| `chunk_size` | Integer | Chunk size in bytes for file uploads (0 if not specified) |
# Portals
> Create, update, fetch, list, and manage MASV Portals — brandable file upload pages with cloud connections and custom settings.
A MASV Portal is a brandable file transfer web page hosted by MASV with a custom URL. End users can visit the page to upload files to MASV. After the sender has uploaded files, MASV notifies the specified email addresses associated with the Portal.
## Custom expiry
[Section titled “Custom expiry”](#custom-expiry)
Each Portal can override the default number of days that MASV stores an uploaded package with the `custom_expiry_days` property. Setting it to `-1` specifies storage for an indefinite period if the [team’s plan](https://masv.io/pricing/) allows it.
Extended Storage charges are incurred beyond the free period included with the team’s plan. For details, see [How does Extended Storage work](https://help.massive.io/en/how-does-extended-storage-work) and [Pricing](https://masv.io/pricing/).
## Regular and private access levels
[Section titled “Regular and private access levels”](#regular-and-private-access-levels)
A Portal’s access level can be *regular* or *private*. By default, all Portals are regular.
If you use a regular Portal, all Team members can view received Packages. If you use a private Portal, only Team members in the Portal’s access list can access the Packages.
Note
Use [Teamspaces](/api/teamspaces/) to limit portal and package access to one or more groups of users within a team. Access to the Private Portals feature is currently restricted because it is deprecated.
## Creating a Portal
[Section titled “Creating a Portal”](#creating-a-portal)
| Method | Route |
| :----- | :---------------------------- |
| `POST` | `/v1/teams/{team_id}/portals` |
### Headers
[Section titled “Headers”](#headers)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Path parameters
[Section titled “Path parameters”](#path-parameters)
| Name | Type | Required | Description |
| --------- | ------ | -------- | --------------------------------- |
| `team_id` | String | Yes | The Team ID to bind the Portal to |
### Body
[Section titled “Body”](#body)
| Name | Type | Required | Description |
| ---------------------------------- | --------- | -------- | ------------------------------------------------------------------------------------------ |
| `name` | String | Yes | Name of the Portal |
| `subdomain` | String | Yes | Subdomain of the Portal |
| `message` | String | No | Message displayed on the Portal upload page |
| `has_access_code` | Boolean | No | Enable/disable access code |
| `access_code` | String | No | Access code for the Portal page |
| `active` | Boolean | No | Enable/disable Portal page. Default: `false` |
| `recipients` | String\[] | No | Email(s) that will receive notifications |
| `has_download_password` | Boolean | No | Enable/disable download password. Default: `false` |
| `download_password` | String | No | Password to protect download access |
| `custom_expiry_days` | Integer | No | Storage days for packages. Range: `-1` to `65535` |
| `cloud_connections` | Object\[] | No | Cloud connections attached to the Portal. See [Cloud Connections](/api/cloud-connections/) |
| `custom_webhooks` | Object\[] | No | Custom webhooks attached to the Portal. See [Webhooks](/api/webhooks/) |
| `tag` | Object | No | Tag object for the Portal. See [Tags](/api/tags/) |
| `access_level` | String | No | `regular` or `private`. Default: `regular` |
| `teamspace_id` | String | No | ID of the Teamspace to bind the Portal to |
| `terms_of_service_enabled` | Boolean | No | Enable/disable Terms of Service checkbox |
| `terms_of_service` | Object | No | Custom terms of service object |
| `package_size_restriction_enabled` | Boolean | No | Enable/disable package size restrictions |
| `max_package_size` | Integer | No | Maximum package size in bytes |
| `max_file_size` | Integer | No | Maximum individual file size in bytes |
| `max_file_count` | Integer | No | Maximum number of files |
| `file_type_restriction_enabled` | Boolean | No | Enable/disable file type restrictions |
| `file_types` | String\[] | No | Allowed file extensions (for example, `".mov"`, `".mp4"`) |
| `expiry_enabled` | Boolean | No | Enable/disable Portal expiry |
| `expiry` | String | No | Portal expiry date-time. Format: [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) |
### Request
[Section titled “Request”](#request)
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -d '{"name": "$NAME", "subdomain": "$SUBDOMAIN", "recipients": [""], "has_access_code": false, "active": true}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/teams/$TEAM_ID/portals
```
### Response
[Section titled “Response”](#response)
Returns `201 Created` with the full Portal object.
## Updating a Portal
[Section titled “Updating a Portal”](#updating-a-portal)
| Method | Route |
| :----- | :------------------------ |
| `PUT` | `/v1/portals/{portal_id}` |
### Headers
[Section titled “Headers”](#headers-1)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Body
[Section titled “Body”](#body-1)
Same fields as creating a Portal. When updating, provide the full Portal object. A typical workflow: Get Portal → Modify Fields → Update Portal.
### Request
[Section titled “Request”](#request-1)
```bash
curl -d '{"name": "$NAME", "subdomain": "$SUBDOMAIN", "active": true, "has_access_code": false}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X PUT https://api.massive.app/v1/portals/$PORTAL_ID
```
### Response
[Section titled “Response”](#response-1)
Returns `200 OK` with the updated Portal object.
## Fetching a Portal
[Section titled “Fetching a Portal”](#fetching-a-portal)
| Method | Route |
| :----- | :------------------------ |
| `GET` | `/v1/portals/{portal_id}` |
### Headers
[Section titled “Headers”](#headers-2)
| Name | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
### Request
[Section titled “Request”](#request-2)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X GET https://api.massive.app/v1/portals/$PORTAL_ID
```
### Response
[Section titled “Response”](#response-2)
Returns `200 OK` with the Portal object.
## Listing Portals
[Section titled “Listing Portals”](#listing-portals)
| Method | Route |
| :----- | :------------------------------ |
| `GET` | `/v1.1/teams/{team_id}/portals` |
### Headers
[Section titled “Headers”](#headers-3)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Query parameters
[Section titled “Query parameters”](#query-parameters)
| Name | Type | Required | Description |
| ------------ | ------- | -------- | --------------------------------------------------------------------------------------------------- |
| `page` | Integer | No | Page number. Default: `1` |
| `limit` | Integer | No | Records per page, 1–50. Default: `50` |
| `sort` | String | No | Sort ascending (`fieldname`) or descending (`-fieldname`). Accepted: `name`, `created_at`, `active` |
| `name` | String | No | Substring match on Portal name |
| `subdomain` | String | No | Substring match on Portal subdomain |
| `tags` | String | No | Comma-separated [tag](/api/tags/) IDs |
| `teamspaces` | String | No | Comma-separated [Teamspace](/api/teamspaces/) IDs |
### Request
[Section titled “Request”](#request-3)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X GET "https://api.massive.app/v1.1/teams/$TEAM_ID/portals?page=1&limit=10"
```
### Response
[Section titled “Response”](#response-3)
Returns `200 OK` with `metadata.total` and `records` array.
## Listing Portal packages
[Section titled “Listing Portal packages”](#listing-portal-packages)
| Method | Route |
| :----- | :----------------------------------- |
| `GET` | `/v1.1/portals/{portal_id}/packages` |
### Headers
[Section titled “Headers”](#headers-4)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Query parameters
[Section titled “Query parameters”](#query-parameters-1)
| Name | Type | Required | Description |
| ------------------ | ------- | -------- | ------------------------------------- |
| `page` | Integer | No | Page number. Default: `1` |
| `limit` | Integer | No | Records per page, 1–50. Default: `50` |
| `sort` | String | No | Sort ascending or descending |
| `tags` | String | No | Comma-separated tag IDs |
| `sender` | String | No | Filter by sender email |
| `created_at_start` | String | No | Created after date (ISO 8601) |
| `created_at_end` | String | No | Created before date (ISO 8601) |
| `expiry_start` | String | No | Expires after date |
| `expiry_end` | String | No | Expires before date |
| `extra_storage` | Boolean | No | Filter by extra storage usage |
### Request
[Section titled “Request”](#request-4)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X GET "https://api.massive.app/v1.1/portals/$PORTAL_ID/packages?page=1&limit=3"
```
### Response
[Section titled “Response”](#response-4)
Returns `200 OK` with `metadata.total` and `records` array of package objects.
# Tags
> Create, list, and delete tags for MASV packages to facilitate better tracking and searching across your Team.
Assign tags to Packages to facilitate better tracking and searching.
## Add a new tag
[Section titled “Add a new tag”](#add-a-new-tag)
| Method | Route |
| :----- | :---------------------- |
| `POST` | `/teams/{team_id}/tags` |
### Headers
[Section titled “Headers”](#headers)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### URL parameters
[Section titled “URL parameters”](#url-parameters)
| Name | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------ |
| `team_id` | String | Yes | The Team ID to bind the tag to |
### Body
[Section titled “Body”](#body)
| Name | Type | Required | Description |
| ------ | ------ | -------- | ------------------------------------- |
| `name` | String | Yes | The name you want to give the new tag |
### Request
[Section titled “Request”](#request)
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -d '{"name": "$NAME"}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/teams/$TEAM_ID/tags
```
### Response
[Section titled “Response”](#response)
After a successful request, this endpoint returns `201 Created`:
```json
{
"id": "01FZGG2QQQTJV9CJ1Q2ZNXVKCQ",
"name": "test",
"team_id": "01FX8CYWMQAEGW8AYSNQRGAMRM",
"active": true,
"created_at": "2022-03-31T17:23:57.815Z",
"updated_at": "2022-03-31T13:23:57.815Z"
}
```
| Property | Description |
| ------------ | -------------------------------------- |
| `id` | The ID of the newly created tag |
| `name` | The name you gave the tag |
| `team_id` | The ID of the Team this tag belongs to |
| `active` | Whether or not this tag is active |
| `created_at` | When this tag was created |
| `updated_at` | When this tag was last updated |
## Fetch a Team’s tags
[Section titled “Fetch a Team’s tags”](#fetch-a-teams-tags)
| Method | Route |
| :----- | :---------------------- |
| `GET` | `/teams/{team_id}/tags` |
### Headers
[Section titled “Headers”](#headers-1)
| Name | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
### URL parameters
[Section titled “URL parameters”](#url-parameters-1)
| Name | Type | Required | Description |
| --------- | ------ | -------- | ---------------------------------------------- |
| `team_id` | String | Yes | The ID of the Team whose tags you are fetching |
### Request
[Section titled “Request”](#request-1)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X GET https://api.massive.app/v1/teams/$TEAM_ID/tags
```
### Response
[Section titled “Response”](#response-1)
After a successful request, this endpoint returns `200 OK`:
```json
[
{
"id": "01FZGG2QQQTJV9CJ1Q2ZNXVKCQ",
"name": "test",
"team_id": "01FX8CYWMQAEGW8AYSNQRGAMRM",
"active": true,
"created_at": "2022-03-31T17:23:57.815Z",
"updated_at": "2022-03-31T13:23:57.815Z"
}
]
```
## Delete a tag
[Section titled “Delete a tag”](#delete-a-tag)
To delete a tag, you will need its ID.
| Method | Route |
| :------- | :--------------- |
| `DELETE` | `/tags/{tag_id}` |
### Headers
[Section titled “Headers”](#headers-2)
| Name | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
### URL parameters
[Section titled “URL parameters”](#url-parameters-2)
| Name | Type | Required | Description |
| -------- | ------ | -------- | ------------------------------------ |
| `tag_id` | String | Yes | The ID of the tag you wish to delete |
### Request
[Section titled “Request”](#request-2)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X DELETE https://api.massive.app/v1/tags/$TAG_ID
```
### Response
[Section titled “Response”](#response-2)
After a successful request, this endpoint returns `204 No Content` with no body.
## Tagging
[Section titled “Tagging”](#tagging)
Tags can be applied to multiple entities. This section covers how to apply tags to entities that support it.
### Tag objects
[Section titled “Tag objects”](#tag-objects)
Tags are received as a JSON object containing one or more of the following properties:
| Name | Type | Description |
| ------ | ------ | ------------------------------------- |
| `id` | String | The ID of the tag you wish to apply |
| `name` | String | The name of the tag you wish to apply |
If `id` is provided, the API checks your team’s tags for a matching ID. If the tag does not exist, no tag is attached and no error is returned.
If `name` is provided and `id` was either not provided or invalid, a tag is found or created on your team using the provided name.
The order of operations for applying tags:
1. If `id` was provided: get tag by ID. If found, use this tag. If not found, continue to name check.
2. If `name` was provided: check if your Team has a tag with this name. If it does, use this tag. If not, create a tag with the provided name and use it.
Note
If the tag object is not provided or is empty, no tag is applied. When updating tags, an empty or missing `tag` results in the current tag being removed.
## Tag a package on creation
[Section titled “Tag a package on creation”](#tag-a-package-on-creation)
Note
This section is for Team packages, not Portal packages. Portal packages are covered in the [Portals](/api/portals/) documentation.
| Method | Route |
| :----- | :-------------------------- |
| `POST` | `/teams/{team_id}/packages` |
### Headers
[Section titled “Headers”](#headers-3)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### URL parameters
[Section titled “URL parameters”](#url-parameters-3)
| Name | Type | Required | Description |
| --------- | ------ | -------- | ----------------------------------------- |
| `team_id` | String | Yes | The ID of the Team to create a package on |
### Body
[Section titled “Body”](#body-1)
| Name | Type | Required | Description |
| -------------- | --------- | -------- | ---------------------------------------------------- |
| `access_limit` | Integer | No | Override default number of downloads for the package |
| `description` | String | Yes | Description of the package |
| `name` | String | Yes | Name of the package |
| `password` | String | No | Password required to download the package |
| `recipients` | String\[] | Yes | Email address of recipient(s) |
| `tag` | Tag | No | A tag object used to set the package’s tag |
### Request
[Section titled “Request”](#request-3)
```bash
curl -d '{"name":"$NAME","description":"$DESCRIPTION","recipients":["$RECIPIENTS"],"tag":{"name":"test tag"}}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/teams/$TEAM_ID/packages
```
### Response
[Section titled “Response”](#response-3)
After a successful request, this endpoint returns `201 Created` with the package object including the applied tag.
## Update a package’s tag
[Section titled “Update a package’s tag”](#update-a-packages-tag)
| Method | Route |
| :----- | :----------------------- |
| `PUT` | `/packages/{package_id}` |
### Headers
[Section titled “Headers”](#headers-4)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | ---------------------------------------- |
| `X-Package-Token` | String | Yes | Package JSON Web Token with write access |
| `Content-Type` | String | Yes | Must be `application/json` |
### URL parameters
[Section titled “URL parameters”](#url-parameters-4)
| Name | Type | Required | Description |
| ------------ | ------ | -------- | ----------------------------------- |
| `package_id` | String | Yes | The ID of the package being updated |
### Body
[Section titled “Body”](#body-2)
When updating a package, provide the complete package object. To update a package’s tag, include a tag object as described in the Tag Objects section above. To remove a tag from a package, remove the `tag` property from the request body.
### Request
[Section titled “Request”](#request-4)
```bash
curl -d '{"tag":{"name":"new tag name"}}' \
-H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-X PUT https://api.massive.app/v1/packages/$PACKAGE_ID
```
Note
The above example only includes the tag field. You should include all package fields in your own requests.
# Teamspaces
> Create, update, delete, and manage MASV Teamspaces to organize Team members into groups for projects, clients, or departments.
Create and manage Teamspaces to organize subsets of your Team members into groups for projects, clients, or departments.
* Control access to files sent to specific Portals.
* Create as many Teamspaces as needed per Team.
* Combine with [Tags](/api/tags/) for an extra level of project tracking and billing.
* See Transfer History and billing in the MASV Web App for each Teamspace.
## Create Teamspace
[Section titled “Create Teamspace”](#create-teamspace)
| Method | Route |
| :----- | :--------------------------- |
| `POST` | `/v1/teams/{team_id}/spaces` |
### Headers
[Section titled “Headers”](#headers)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### URL parameters
[Section titled “URL parameters”](#url-parameters)
| Name | Type | Required | Description |
| --------- | ------ | -------- | --------------------------------------- |
| `team_id` | String | Yes | The ID of the Team to own the Teamspace |
### Body
[Section titled “Body”](#body)
| Name | Type | Required | Description |
| ------------ | --------- | -------- | -------------------------------------------------------- |
| `name` | String | Yes | Name of the Teamspace to create |
| `member_ids` | String\[] | No | Membership ID(s) of Team members to add to the Teamspace |
### Request
[Section titled “Request”](#request)
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -d '{"name": "$NAME", "member_ids": [""]}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/teams/$TEAM_ID/spaces
```
### Response
[Section titled “Response”](#response)
Returns `201 Created`:
```json
{
"id": "01E8TP2TJCTDNW11G67NKHQW5J",
"name": "Marketing-space",
"members": [
{
"id": "01H6CTJJJ19PVMR0J0JXGFK96D",
"name": "Jim Halpert",
"user_id": "01H6CTJJHQAKKWJ50Y635NNEPD"
}
]
}
```
## Get Teamspace
[Section titled “Get Teamspace”](#get-teamspace)
| Method | Route |
| :----- | :---------------------- |
| `GET` | `/v1/spaces/{space_id}` |
### Headers
[Section titled “Headers”](#headers-1)
| Name | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
### Request
[Section titled “Request”](#request-1)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X GET https://api.massive.app/v1/spaces/$SPACE_ID
```
### Response
[Section titled “Response”](#response-1)
Returns `200 OK` with the Teamspace object including its members.
## List Teamspaces
[Section titled “List Teamspaces”](#list-teamspaces)
| Method | Route |
| :----- | :----------------------------- |
| `GET` | `/v1.1/teams/{team_id}/spaces` |
### Headers
[Section titled “Headers”](#headers-2)
| Name | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
### Query parameters
[Section titled “Query parameters”](#query-parameters)
| Name | Type | Required | Description |
| ------- | ------- | -------- | ------------------------------------------------ |
| `page` | Integer | No | Page number. Default: `0` |
| `limit` | Integer | No | Maximum records to fetch (50 max). Default: `50` |
### Request
[Section titled “Request”](#request-2)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X GET https://api.massive.app/v1.1/teams/$TEAM_ID/spaces
```
### Response
[Section titled “Response”](#response-2)
Returns `200 OK` with a paginated list of Teamspaces.
## Update Teamspace
[Section titled “Update Teamspace”](#update-teamspace)
| Method | Route |
| :----- | :---------------------- |
| `PUT` | `/v1/spaces/{space_id}` |
### Headers
[Section titled “Headers”](#headers-3)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Body
[Section titled “Body”](#body-1)
| Name | Type | Required | Description |
| ------ | ------ | -------- | ---------------------------- |
| `name` | String | Yes | A new name for the Teamspace |
### Request
[Section titled “Request”](#request-3)
```bash
curl -H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X PUT https://api.massive.app/v1/spaces/$SPACE_ID \
-d '{"name": "$NAME"}'
```
### Response
[Section titled “Response”](#response-3)
Returns `200 OK` with the updated Teamspace object.
## Delete Teamspace
[Section titled “Delete Teamspace”](#delete-teamspace)
| Method | Route |
| :------- | :---------------------- |
| `DELETE` | `/v1/spaces/{space_id}` |
### Request
[Section titled “Request”](#request-4)
```bash
curl -X DELETE \
-H "X-API-KEY: $API_KEY" \
https://api.massive.app/v1/spaces/$SPACE_ID
```
### Response
[Section titled “Response”](#response-4)
Returns `204 No Content`.
## List Teamspace packages
[Section titled “List Teamspace packages”](#list-teamspace-packages)
| Method | Route |
| :----- | :--------------------------------- |
| `GET` | `/v1.1/spaces/{space_id}/packages` |
### Headers
[Section titled “Headers”](#headers-4)
| Name | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
### Query parameters
[Section titled “Query parameters”](#query-parameters-1)
| Name | Type | Required | Description |
| ------------------ | ------- | -------- | ----------------------------------------------------------------------------------------- |
| `page` | Integer | No | Page number. Default: `0` |
| `limit` | Integer | No | Maximum records (1–100) |
| `sort` | String | No | Sort ascending (`fieldname`) or descending (`-fieldname`) |
| `status` | String | No | Filter by state: `new`, `finalized`, `expired`, `archived`. Default: `finalized, expired` |
| `name` | String | No | Filter by package name (partial match) |
| `sender` | String | No | Filter by sender email |
| `tags` | String | No | Comma-separated tag IDs |
| `created_at_start` | String | No | Filter by creation date (YYYY-MM-DD) |
| `created_at_end` | String | No | Filter by creation date (YYYY-MM-DD) |
### Request
[Section titled “Request”](#request-5)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X GET "https://api.massive.app/v1.1/spaces/$SPACE_ID/packages?page=1&tags=engineering,marketing"
```
### Response
[Section titled “Response”](#response-5)
Returns `200 OK` with an array of package objects.
## Add Team members to Teamspace
[Section titled “Add Team members to Teamspace”](#add-team-members-to-teamspace)
While Team Owners and Admins already have read and write access to all Teamspaces, Team members need to be added to access a Teamspace. A Team member can be added to multiple Teamspaces.
| Method | Route |
| :----- | :------------------------------ |
| `POST` | `/v1/spaces/{space_id}/members` |
### Headers
[Section titled “Headers”](#headers-5)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Body
[Section titled “Body”](#body-2)
| Name | Type | Required | Description |
| ------------ | --------- | -------- | --------------------------------------- |
| `member_ids` | String\[] | Yes | Membership ID(s) of Team members to add |
### Request
[Section titled “Request”](#request-6)
```bash
curl -d '{"member_ids": [""]}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/spaces/$SPACE_ID/members
```
### Response
[Section titled “Response”](#response-6)
Returns `201 Created` with an array of Teamspace membership objects.
## List Teamspace members
[Section titled “List Teamspace members”](#list-teamspace-members)
| Method | Route |
| :----- | :------------------------------ |
| `GET` | `/v1/spaces/{space_id}/members` |
### Request
[Section titled “Request”](#request-7)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X GET https://api.massive.app/v1/spaces/$SPACE_ID/members
```
### Response
[Section titled “Response”](#response-7)
Returns `200 OK` with an array of Teamspace membership objects.
## Remove members from Teamspace
[Section titled “Remove members from Teamspace”](#remove-members-from-teamspace)
| Method | Route |
| :------- | :-------------------------------------------------------- |
| `DELETE` | `/v1/spaces/{space_id}/members/{teamspace_membership_id}` |
### Request
[Section titled “Request”](#request-8)
```bash
curl -X DELETE \
-H "X-API-KEY: $API_KEY" \
https://api.massive.app/v1/spaces/$SPACE_ID/members/$TEAMSPACE_MEMBERSHIP_ID
```
### Response
[Section titled “Response”](#response-8)
Returns `204 No Content`.
# Web Tokens
> Understand MASV JSON Web Tokens for package access and cloud transfer authorization.
The MASV API uses [JSON Web Token (JWT)](https://en.wikipedia.org/wiki/JSON_Web_Token) to authorize package and transfer operations. These requests must have the appropriate header field:
* `X-Package-Token`: For accessing a package. Examples: [uploads](/api/uploads/) and [downloads](/api/downloads/).
* `X-Transfer-Token`: For accessing a transfer to and from a [cloud connection](/api/cloud-connections/).
Danger
Do not use `X-User-Token` for API integrations. User tokens require storing passwords, bypass MFA, and expose your full account privileges. Use [API keys](/api/api-keys/) instead — they are scoped, rotatable, and do not require credential storage.
## Package tokens
[Section titled “Package tokens”](#package-tokens)
The MASV API requires special JWTs to interact with packages. These tokens are authorized to interact with a single package, rather than all packages.
Package tokens come in one of three variants, depending on the authentication mechanism used to request them:
* **Write access** (limited): Granted by the initial [create Portal package](/api/uploads/#step-1b--create-a-portal-package) request. Restricted to actions needed to complete the upload — creating files and finalizing the package — without the ability to create links, edit the package expiry, or initiate transfers to connected storage.
* **Read access** (limited): Granted by [authenticating with link credentials](/api/downloads/). Used primarily for downloading package files. Limited to reading package details, though it does allow clients to initiate transfers to cloud storage when providing single-use credentials.
* **Management access**: Granted when authenticating with user credentials (API key) for managing packages owned by the account. Returned when fetching the list of [packages](/api/packages/) or when [sending a package](/api/uploads/#step-1a--create-a-team-package). Permits editing package details (name, expiry), creating additional links, deleting the package, and viewing or initiating transfers to [connected storage integrations](/api/cloud-connections/). Does not directly permit downloads — a link must be created and authenticated to get a read access token.
For endpoints that require it, your request must have the `X-Package-Token` header set:
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | -------------------------------------- |
| `X-Package-Token` | String | Yes | `access_token` from a package response |
## Transfer tokens
[Section titled “Transfer tokens”](#transfer-tokens)
The MASV API requires special JWTs to interact with transfers to [connected storage](/api/cloud-connections/). These tokens are authorized to interact with an individual transfer and are limited to reading the transfer status, retrying it, or cancelling it. Transfer tokens are returned when [initiating a new transfer](/api/cloud-connections/#initiate-a-manual-package-transfer) or when fetching the list of transfers for a specific package.
For endpoints that require it, your request must have the `X-Transfer-Token` header set:
| Name | Type | Required | Description |
| ------------------ | ------ | -------- | --------------------------------------- |
| `X-Transfer-Token` | String | Yes | `access_token` from a transfer response |
# Upload files with the MASV API
> Upload files to MASV using the API — create packages, add files, obtain upload URLs, upload chunks, and finalize transfers.
Upload files directly to MASV’s private cloud infrastructure using the API.
Tip
The recommended way to upload files is to use [MASV Agent](/agent/). It abstracts the complexities of directly using the MASV API for uploading and offers features for managing automations, multiple connections, rate limits, and local storage. Use the MASV API for uploading when your application’s host OS is not supported by the MASV Agent or you need finer-grained control.
Be aware of storage expiry when planning your upload workflow:
Caution
MASV stores an uploaded package in MASV storage until its expiry time. When packages expire, MASV deletes the package and its content. For details, see [Pricing](https://masv.io/pricing/) and [How extended storage works](https://help.massive.io/en/how-does-extended-storage-work).
## Packages and authorization
[Section titled “Packages and authorization”](#packages-and-authorization)
Files uploaded to MASV belong to a package object — a virtual directory inside of MASV. Each package contains a token that can be used to upload files to that specific virtual directory. A package can be created by interacting with different API endpoints (for example, Portals or Teams).
All requests to upload endpoints require a [token](/api/tokens/) passed as an HTTP header, `X-Package-Token`. This token authorizes the user to add and remove files from the package until the package is finalized.
## Lifecycle of an upload
[Section titled “Lifecycle of an upload”](#lifecycle-of-an-upload)
For each file in the package:
1. Add file to the package (API action)
2. Create the file in cloud storage
3. Collect metadata from cloud storage service
4. Identify the number of file chunks
5. Obtain authorized URLs to upload each chunk
6. Upload all chunks to cloud storage
7. Collect metadata for each uploaded chunk
8. After all chunks are uploaded, finalize the file
Then finalize the package.
Diagram source
```plaintext
sequenceDiagram
participant Client
participant API as MASV API
participant Storage as Cloud Storage
Client->>API: Create Package
API-->>Client: Package ID + access token
loop For each file
Client->>API: Add file to Package
API-->>Client: Create blueprint
Client->>Storage: Create file in cloud storage (blueprint)
Storage-->>Client: Upload ID
Client->>API: Obtain upload URLs (chunk count)
API-->>Client: Pre-signed URLs (blueprints)
loop For each chunk
Client->>Storage: Upload chunk (PUT)
Storage-->>Client: ETag
end
Client->>API: Finalize file (ETags + upload ID)
API-->>Client: File finalized
end
Client->>API: Finalize Package
API-->>Client: Package finalized — delivery triggered
```
The upload lifecycle sequence: the client creates a Package via the API and receives an access token. For each file, the client adds the file to the Package, creates it in cloud storage using a blueprint, obtains pre-signed upload URLs for each chunk, uploads the chunks directly to cloud storage, then finalizes the file. After all files are uploaded, the client finalizes the Package to trigger delivery.
Note
After the uploaded package has been finalized, the transfer is locked and cannot be further modified. Finalization dispatches the file to the intended destination immediately.
## MASV blueprints
[Section titled “MASV blueprints”](#masv-blueprints)
MASV’s API abstracts interactions with cloud storage services by creating a Blueprint object with four properties:
| Name | Type | Description |
| --------- | ------ | ----------------------------------------------- |
| `url` | String | URL that the request should be forwarded to |
| `method` | String | The HTTP method to use (GET, POST, PUT, DELETE) |
| `headers` | JSON | Key-value map of header names and their values |
| `body` | String | HTTP request body |
## Chunk size
[Section titled “Chunk size”](#chunk-size)
MASV lets you set the chunk size, which divides larger files into segments for transfer. The default chunk size is 100 MiB, but you can adjust the `chunk_size` parameter to optimize for speed or reliability.
Storage services like Amazon S3 limit the number and size range for chunks. MASV uses the endpoint `https://api.massive.app/v1/system/packages/spec` to pass chunk parameters:
| Parameter | Description |
| ----------------- | -------------------------------------------------- |
| `max_chunk_count` | Maximum number of chunks permitted (10,000 for S3) |
| `max_chunk_size` | Maximum chunk size in bytes (5 GiB for S3) |
| `min_chunk_size` | Minimum chunk size in bytes (5 MiB for S3) |
## Upload API interaction
[Section titled “Upload API interaction”](#upload-api-interaction)
Each package belongs to either a Team or Portal on MASV. To create a package for a Team, you must provide an [API key](/api/api-keys/). To create a package for a Portal, you do not need an API key (but must provide an access code if the Portal requires it).
### Step 1a — Create a Team package
[Section titled “Step 1a — Create a Team package”](#step-1a--create-a-team-package)
| Method | Route |
| :----- | :-------------------------- |
| `POST` | `/teams/{team_id}/packages` |
#### Headers
[Section titled “Headers”](#headers)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
#### Body
[Section titled “Body”](#body)
| Name | Type | Required | Description |
| ------------------- | --------- | -------- | -------------------------------------------------------- |
| `access_limit` | Integer | No | Override default number of downloads |
| `description` | String | Yes | Description of the package |
| `name` | String | Yes | Name of the package |
| `password` | String | No | Password required to download |
| `recipients` | String\[] | Yes | Email address of recipient(s) |
| `unlimited_storage` | Boolean | No | Enable unlimited extended storage |
| `chunk_size` | Integer | No | Chunk size in bytes for all file uploads in this package |
#### Request
[Section titled “Request”](#request)
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -d '{"access_limit":$ACCESS_LIMIT, "description":"$DESCRIPTION", "name":"$NAME", "password": "$PASSWORD", "recipients":["$RECIPIENT_EMAIL"]}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/teams/$TEAM_ID/packages
```
#### Response
[Section titled “Response”](#response)
Returns `201 Created` with the package object including `id` and `access_token`.
Note
Take note of the `id` and the `access_token` as they are required to interact with the upload API.
### Step 1b — Create a Portal package
[Section titled “Step 1b — Create a Portal package”](#step-1b--create-a-portal-package)
Tip
If a Custom Form is enabled for the Portal, you must [submit a form response](/api/custom-metadata/#submitting-a-form-response) for required custom fields before the package can be created.
| Method | Route |
| :----- | :------------------------------ |
| `POST` | `/portals/{portal_id}/packages` |
#### Headers
[Section titled “Headers”](#headers-1)
| Name | Type | Required | Description |
| --------------- | ------ | -------- | ------------------------------------------------------------ |
| `Content-Type` | String | Yes | Must be `application/json` |
| `X-Access-Code` | String | No | URI encoded upload password (if required by Portal settings) |
#### Body
[Section titled “Body”](#body-1)
| Name | Type | Required | Description |
| -------------- | ------- | -------- | ---------------------------------------------------------------------- |
| `description` | String | Yes | Description of the package |
| `name` | String | Yes | Name of the package |
| `sender` | String | Yes | Email address of the Portal package sender |
| `form_data_id` | String | No\* | Form response ID. \*Required if the Portal has an enabled custom form. |
| `chunk_size` | Integer | No | Chunk size in bytes |
### Step 2: Add a file to the package
[Section titled “Step 2: Add a file to the package”](#step-2-add-a-file-to-the-package)
| Method | Route |
| :----- | :----------------------------- |
| `POST` | `/packages/{package_id}/files` |
#### Headers
[Section titled “Headers”](#headers-2)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | -------------------------- |
| `X-Package-Token` | String | Yes | Package JSON Web Token |
| `Content-Type` | String | Yes | Must be `application/json` |
#### Body
[Section titled “Body”](#body-2)
| Name | Type | Required | Description |
| --------------- | ------- | -------- | ----------------------------------------- |
| `kind` | String | Yes | Type of file: `file` or `directory` |
| `name` | String | Yes | File name |
| `path` | String | Yes | File’s relative path |
| `last_modified` | String | Yes | File’s last modified date (UTC) |
| `size` | Integer | No | File size in bytes (strongly recommended) |
| `chunk_size` | Integer | No | Override the package-level chunk size |
#### Request
[Section titled “Request”](#request-1)
```bash
curl -d '{"kind":"file", "name":"my_video.mpeg", "path": "", "last_modified":"2018-12-17T16:14:34.450Z"}' \
-H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/packages/$PACKAGE_ID/files
```
#### Response
[Section titled “Response”](#response-1)
Returns `201 Created` with `create_blueprint` and `file` objects.
### Step 3: Create the file in MASV’s cloud storage
[Section titled “Step 3: Create the file in MASV’s cloud storage”](#step-3-create-the-file-in-masvs-cloud-storage)
Use the `create_blueprint` from step 2 to construct an HTTP request to the cloud storage service. The response is XML containing an `UploadId` needed for subsequent steps.
### Step 4: Obtain upload URLs
[Section titled “Step 4: Obtain upload URLs”](#step-4-obtain-upload-urls)
Request pre-signed upload URLs for each of a file’s parts.
| Method | Route |
| :----- | :------------------------------------------------------------------- |
| `POST` | `/packages/{package_id}/files/{file_id}?start={start}&count={count}` |
#### Headers
[Section titled “Headers”](#headers-3)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | -------------------------- |
| `X-Package-Token` | String | Yes | Package JSON Web Token |
| `Content-Type` | String | Yes | Must be `application/json` |
#### Body
[Section titled “Body”](#body-3)
| Name | Type | Required | Description |
| ----------- | ------ | -------- | -------------------------------------- |
| `upload_id` | String | Yes | ID for the initiated multi-part upload |
#### Query parameters
[Section titled “Query parameters”](#query-parameters)
| Name | Type | Required | Description |
| ------- | ------- | -------- | ------------------------------------------ |
| `start` | Integer | Yes | Starting index of the chunk (zero-indexed) |
| `count` | Integer | Yes | Number of chunk requests to generate |
#### Request
[Section titled “Request”](#request-2)
```bash
curl -d '{"upload_id":"$UPLOAD_ID"}' \
-H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-X POST "https://api.massive.app/v1/packages/$PACKAGE_ID/files/$FILE_ID?start=$START&count=$COUNT"
```
#### Response
[Section titled “Response”](#response-2)
Returns an array of blueprints in ascending order based on the chunk index.
Tip
Do not request more chunk URLs than necessary and do not skip any of the URLs provided.
### Step 5: Upload the file chunks
[Section titled “Step 5: Upload the file chunks”](#step-5-upload-the-file-chunks)
Use each blueprint to upload the corresponding chunk as binary data in the request body.
```bash
curl -X PUT "$BLUEPRINT_URL" \
--data-binary @<(dd if=my_video.mpeg skip=0 count=104857600 iflag=skip_bytes,count_bytes)
```
Note
For each uploaded chunk, collect the `partNumber` (from the URL parameters) and `ETag` (from the response header).
### Step 6: Publish upload progress
[Section titled “Step 6: Publish upload progress”](#step-6-publish-upload-progress)
Note
This feature is only available to customers with this feature enabled. Contact to request access.
MASV integrates with [PubNub](https://www.pubnub.com/) to indicate overall file upload progress on each package.
### Step 7: Finalize the file
[Section titled “Step 7: Finalize the file”](#step-7-finalize-the-file)
After all chunks have been uploaded, inform the API that the file upload is complete.
| Method | Route |
| :----- | :------------------------------------------------ |
| `POST` | `/packages/{package_id}/files/{file_id}/finalize` |
#### Headers
[Section titled “Headers”](#headers-4)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | -------------------------- |
| `X-Package-Token` | String | Yes | Package JSON Web Token |
| `Content-Type` | String | Yes | Must be `application/json` |
#### Body
[Section titled “Body”](#body-4)
| Name | Type | Required | Description |
| ---------------------------- | --------- | ----------- | ---------------------------------- |
| `chunk_extras` | Object\[] | Yes | Information about all chunks |
| `chunk_extras[].part_number` | String | Yes | Chunk part number |
| `chunk_extras[].etag` | String | Yes | Chunk part hash |
| `file_extras.upload_id` | String | Yes | The upload ID from step 3 |
| `size` | Integer | Yes | The file size |
| `chunk_size` | Integer | Recommended | The chunk size used when uploading |
#### Request
[Section titled “Request”](#request-3)
```bash
curl -d '{"chunk_extras":[{"part_number":"1","etag":"\"7be1b3cc95a04a6dd07d157ad6fae64a\""}], "file_extras":{"upload_id":"$UPLOAD_ID"}, "size":$FILE_SIZE, "chunk_size":$CHUNK_SIZE}' \
-H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/packages/$PACKAGE_ID/files/$FILE_ID/finalize
```
#### Response
[Section titled “Response”](#response-3)
Returns `204 No Content`.
Note
Repeat steps 2–7 for each additional file in the package.
Each file must match its declared size exactly:
Caution
If the file size submitted does not match the actual size of the object in storage, the service will respond with `409 Conflict`.
### Step 8: Finalize the package
[Section titled “Step 8: Finalize the package”](#step-8-finalize-the-package)
After all files have been uploaded and finalized, indicate that the package is ready to be dispatched.
| Method | Route |
| :----- | :-------------------------------- |
| `POST` | `/packages/{package_id}/finalize` |
#### Headers
[Section titled “Headers”](#headers-5)
| Name | Type | Required | Description |
| ----------------- | ------ | -------- | -------------------------- |
| `X-Package-Token` | String | Yes | Package JSON Web Token |
| `Content-Type` | String | Yes | Must be `application/json` |
#### Request
[Section titled “Request”](#request-4)
```bash
curl -H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/packages/$PACKAGE_ID/finalize
```
#### Response
[Section titled “Response”](#response-4)
Returns `204 No Content`.
Caution
Make sure all uploaded files have been completed and finalized before finalizing the package. Any in-flight, non-finalized files will be dropped. If no files were uploaded/finalized, this API call will fail.
## Creating additional links
[Section titled “Creating additional links”](#creating-additional-links)
You can create additional direct-download links or send a link to a specific email recipient after the upload has been finalized. See the [Links](/api/links/#creating-links) page for more details.
# Webhooks
> Create and manage custom webhooks to receive JSON notifications for MASV Portal events like package creation and finalization.
Connect your MASV account to external web services to receive notifications via custom webhooks.
Note
This feature is only available for a limited number of customers. To request webhooks API access for your Team, please contact .
## Create webhook
[Section titled “Create webhook”](#create-webhook)
You can create custom webhooks for any Team you belong to, subject to the [access policy](https://help.massive.io/en/what-are-the-permissions-for-owner-admins-and-members-of-your-team).
| Method | Route |
| :----- | :--------------------------------- |
| `POST` | `/teams/{team_id}/custom_webhooks` |
### Headers
[Section titled “Headers”](#headers)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Body
[Section titled “Body”](#body)
| Name | Type | Required | Description |
| ------------- | --------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `active` | Boolean | Yes | Indicates if the webhook is active or not |
| `body_extras` | Object | No | A static list of keys and values to be appended to the body of all events. All values must be strings. |
| `events` | String\[] | Yes | Indicates which events this webhook will receive (see supported events below) |
| `headers` | Object | No | A static list of header keys and values to be included on every event that is sent |
| `method` | String | Yes | Verb used when event is sent — `POST` and `PUT` are currently supported |
| `name` | String | No | Optional internal reference to the webhook and its purpose |
| `url` | String | Yes | The HTTP or HTTPS endpoint for events to be sent to |
### Request
[Section titled “Request”](#request)
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -d '{"active": true, "events": ["package.created", "package.finalized"], "method": "$METHOD", "name": "$NAME", "url": "$URL"}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/teams/$TEAM_ID/custom_webhooks
```
Caution
The total size of URL + headers + extras + event payload cannot exceed 128 KiB. HTTPS endpoints must have a valid TLS 1.2 or 1.3 certificate.
### Response
[Section titled “Response”](#response)
After a successful request, this endpoint returns `201 Created`:
```json
{
"active": true,
"body_extras": {
"key": "value",
"key2": "value2"
},
"events": ["package.created", "package.finalized"],
"id": "ACHSFSDI32343ASD",
"headers": {
"key": "value",
"key2": "value2"
},
"method": "POST",
"name": "Portal Uploads",
"url": "https://mywebhooks.endpoint"
}
```
Note
Webhook endpoints must use HTTPS with a valid TLS certificate. HTTP endpoints are not supported.
## Update webhook
[Section titled “Update webhook”](#update-webhook)
You can update custom webhooks for any Team you belong to, subject to the [access policy](https://help.massive.io/en/what-are-the-permissions-for-owner-admins-and-members-of-your-team).
| Method | Route |
| :----- | :------------------------------------- |
| `PUT` | `/custom_webhooks/{custom_webhook_id}` |
### Headers
[Section titled “Headers”](#headers-1)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Body
[Section titled “Body”](#body-1)
Same fields as the create webhook endpoint above.
### Request
[Section titled “Request”](#request-1)
```bash
curl -d '{"active": true, "events": ["package.created", "package.finalized"], "method": "$METHOD", "name": "$NAME", "url": "$URL"}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X PUT https://api.massive.app/v1/custom_webhooks/$CUSTOM_WEBHOOK_ID
```
### Response
[Section titled “Response”](#response-1)
After a successful request, this endpoint returns `200 OK` with the updated webhook object.
## Delete webhook
[Section titled “Delete webhook”](#delete-webhook)
| Method | Route |
| :------- | :------------------------------------- |
| `DELETE` | `/custom_webhooks/{custom_webhook_id}` |
### Headers
[Section titled “Headers”](#headers-2)
| Name | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
### Request
[Section titled “Request”](#request-2)
```bash
curl -H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X DELETE https://api.massive.app/v1/custom_webhooks/$CUSTOM_WEBHOOK_ID
```
### Response
[Section titled “Response”](#response-2)
Returns `204 No Content`. Any transfers in progress at the time of deletion will complete normally.
## List webhooks
[Section titled “List webhooks”](#list-webhooks)
| Method | Route |
| :----- | :--------------------------------- |
| `GET` | `/teams/{team_id}/custom_webhooks` |
### Headers
[Section titled “Headers”](#headers-3)
| Name | Type | Required | Description |
| ----------- | ------ | -------- | ------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
### Request
[Section titled “Request”](#request-3)
```bash
curl -H "X-API-KEY: $API_KEY" \
-X GET https://api.massive.app/v1/teams/$TEAM_ID/custom_webhooks
```
### Response
[Section titled “Response”](#response-3)
Returns `200 OK` with an array of webhook objects. The `connections` property indicates the number of Portals attached to each webhook.
## Attach custom webhook to Portal
[Section titled “Attach custom webhook to Portal”](#attach-custom-webhook-to-portal)
Custom webhooks can be attached to a Portal on either the Create or Update Portal methods via the `custom_webhooks` property. The complete list must be passed on every update — any omissions are removed unless the `custom_webhooks` property is excluded entirely.
| Method | Route |
| :----- | :--------------------- |
| `PUT` | `/portals/{portal_id}` |
### Headers
[Section titled “Headers”](#headers-4)
| Name | Type | Required | Description |
| -------------- | ------ | -------- | -------------------------- |
| `X-API-KEY` | String | Yes | [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
### Body
[Section titled “Body”](#body-2)
| Name | Type | Required | Description |
| ----------------- | ----- | -------- | ------------------------------------------ |
| `custom_webhooks` | Array | No | List of webhook objects with `id` property |
### Request
[Section titled “Request”](#request-4)
Note
When updating Portals, provide the full Portal object. The following example is a reference for adding custom webhooks only.
```bash
curl -d '{"name": "$NAME", "subdomain": "$PORTAL_SUBDOMAIN", "custom_webhooks": [{"id":""}, {"id":""}], "has_access_code": false, "active": true}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X PUT https://api.massive.app/v1/portals/$PORTAL_ID
```
Caution
All attached custom webhooks must be provided in each PUT request or they are removed.
## List attached webhooks
[Section titled “List attached webhooks”](#list-attached-webhooks)
Retrieve the list of Portals to see their attached custom webhooks.
| Method | Route |
| :----- | :------------------------- |
| `GET` | `/teams/{team_id}/portals` |
```bash
curl -H "X-API-KEY: $API_KEY" \
-X GET https://api.massive.app/v1/teams/$TEAM_ID/portals
```
## Event payload
[Section titled “Event payload”](#event-payload)
Webhook events are sent for successful actions in MASV. The payload follows a standard format with small variations based on the source object. Events are emitted from MASV to each connected webhook and are attempted up to 5 times with an incremental back-off between attempts.
### package.created
[Section titled “package.created”](#packagecreated)
```json
{
"event_id": "SFUHDF35DSD88F",
"event_time": "2021-01-14T10:39:45.334Z",
"event_type": "package.created",
"body_extras": {
"key": "value"
},
"object": {
"type": "package",
"id": "JF4D076SA6FA1",
"name": "Sample Upload",
"portal_id": "01CYCWJC40RXPK3HNQVYKAX1K1",
"sender": "test@masv.io",
"size": 0,
"state": "new",
"total_files": 0,
"created_at": "2021-01-14T10:39:45.334Z",
"updated_at": "2021-01-14T10:39:45.334Z"
},
"custom_webhook_id": "ACHSFSDI32343ASD"
}
```
| Property | Description |
| ------------------- | -------------------------------------------------------------------- |
| `event_id` | Unique identifier for the webhook event |
| `event_time` | Timestamp for when the event was generated |
| `event_type` | Subject of the event (`package.created` or `package.finalized`) |
| `body_extras` | Key/value pairs supplied on the webhook |
| `object` | The actual record (a package in this case) |
| `object.portal_id` | Portal the package was uploaded to (omitted if not a Portal package) |
| `custom_webhook_id` | Identifier of the webhook this event was associated with |
### package.finalized
[Section titled “package.finalized”](#packagefinalized)
```json
{
"event_id": "SFUHDF35DSD88F",
"event_time": "2021-01-14T16:39:56.350Z",
"event_type": "package.finalized",
"body_extras": {
"key": "value"
},
"object": {
"type": "package",
"id": "JF4D076SA6FA1",
"name": "Sample Upload",
"portal_id": "01CYCWJC40RXPK3HNQVYKAX1K1",
"sender": "test@masv.io",
"size": 33454545,
"state": "finalized",
"total_files": 30,
"created_at": "2021-01-14T10:39:45.334Z",
"updated_at": "2021-01-14T16:39:56.350Z"
},
"custom_webhook_id": "ACHSFSDI32343ASD"
}
```
The `size` and `total_files` fields are populated on the `package.finalized` event (they are 0 during `package.created`).
## Security considerations
[Section titled “Security considerations”](#security-considerations)
MASV webhooks do not currently include a cryptographic signature for payload verification. To secure your webhook endpoint:
* **Use HTTPS** — All webhook URLs must use HTTPS with a valid TLS certificate.
* **Use the `headers` field** — Include a shared secret as a custom header when creating the webhook. Validate this header on your receiving endpoint to confirm the request originated from MASV.
* **Restrict by IP** — If your infrastructure supports it, allowlist MASV’s outbound IP ranges.
* **Validate the payload structure** — Check that `event_type`, `custom_webhook_id`, and `object.id` are present and well-formed before processing.
# Solutions
> Ready-to-deploy integration and partner solutions for production file transfer workflows.
This section provides end-to-end deployment guides. Each solution includes architecture overviews, infrastructure-as-code templates, and step-by-step deployment instructions.
## Available solutions
[Section titled “Available solutions”](#available-solutions)
* **[MASV + LucidLink](/solutions/masv-lucidlink/)** — AWS-based solution for automated file delivery from MASV to LucidLink via S3, SQS, Lambda, and ECS.
# MASV + LucidLink
> Deploy automated file delivery from MASV Portals to LucidLink Filespaces using S3, SQS, Lambda, and ECS on AWS.
Automatically deliver files uploaded through a MASV [Portal](/api/portals/) into a LucidLink Filespace using AWS event-driven infrastructure. MASV Portals combined with LucidLink Connect provide seamless, high-speed ingestion of large files directly into cloud-backed, instantly accessible LucidLink workspaces.
The solution leverages Amazon S3 as the underlying storage layer, with AWS event-driven services orchestrating the integration between MASV and LucidLink Connect APIs.
The pipeline automatically makes uploaded files available in LucidLink without manual intervention. MASV is responsible for high-performance file transfer and user interaction, while AWS provides the event processing and integration logic, and LucidLink provides indexed, filesystem-style access to the data stored in S3.
This architecture cleanly separates ingestion, storage, and indexing across purpose-built components.
## Architecture overview
[Section titled “Architecture overview”](#architecture-overview)
The following diagram shows the end-to-end pipeline from MASV Portal upload through to LucidLink file registration:
Diagram source
```plaintext
flowchart TD
A["MASV Portal\n(File Upload)"] -->|"S3 Integration"| B["Amazon S3\n(Object Storage)"]
B -->|"ObjectCreated Event"| C["Amazon SQS\n(Event Buffer)"]
C -->|"Trigger"| D["AWS Lambda\n(Processing)"]
D -->|"Register File API"| E["Amazon ECS\n(LucidLink Connect API)"]
E -->|"Index File"| F["LucidLink\n(Filespace)"]
style A fill:#03D6B3,stroke:#092F45,color:#0A0A0A
style B fill:#FF9900,stroke:#092F45,color:#0A0A0A
style C fill:#FF9900,stroke:#092F45,color:#0A0A0A
style D fill:#FF9900,stroke:#092F45,color:#0A0A0A
style E fill:#FF9900,stroke:#092F45,color:#0A0A0A
style F fill:#6B5CE7,stroke:#092F45,color:#FFFFFF
```
Pipeline diagram: MASV Portal uploads files via S3 integration to Amazon S3. S3 emits ObjectCreated events to Amazon SQS. SQS triggers an AWS Lambda function. Lambda calls the LucidLink Connect API running on Amazon ECS. ECS indexes the file into the LucidLink Filespace.
## High-level architecture and flow
[Section titled “High-level architecture and flow”](#high-level-architecture-and-flow)
At a high level, the solution operates as an event-driven pipeline triggered by file uploads. When you upload content through a MASV Portal configured with an [S3 cloud connection](/api/cloud-connections/), the files are written directly into a designated S3 bucket. This bucket serves as the authoritative storage location for all uploaded objects.
Amazon S3 is configured to emit `ObjectCreated` events whenever new files are written. These events are not sent directly to compute services; instead, they are delivered to an Amazon SQS queue. This design introduces a durable buffering layer that decouples file ingestion from downstream processing, improving reliability and scalability.
An AWS Lambda function is configured to consume messages from the SQS queue. For each event, the Lambda function extracts the relevant object metadata, including bucket name and object key, and constructs a request to the LucidLink Connect API. This API call registers the new file within a LucidLink Filespace, making it immediately visible to users accessing LucidLink.
The LucidLink API is hosted within an Amazon Elastic Container Service (Amazon ECS) service running a containerized instance of the LucidLink Connect API. Communication between Lambda and this API occurs entirely within a Virtual Private Cloud (VPC) using AWS Cloud Map for service discovery, ensuring that no public endpoints are exposed.
## Amazon S3 configuration
[Section titled “Amazon S3 configuration”](#amazon-s3-configuration)
Amazon S3 serves as the central storage layer in this architecture and is the destination for all MASV uploads. The MASV Portal is configured with an S3 cloud connection such that uploaded files are written directly into a specified bucket without intermediary processing.
To enable downstream automation, the S3 bucket is configured with event notifications for all object creation events. These notifications are directed to an Amazon Simple Queue Service (Amazon SQS) queue rather than directly invoking compute services. This indirection is critical for ensuring reliable event delivery and supporting retry mechanisms.
For S3 to publish messages to SQS, an explicit access policy must be applied to the queue. This policy grants the S3 service principal permission to send messages, scoped to the specific bucket Amazon Resource Name (ARN) and AWS account. Without this configuration, event delivery will fail silently.
## Amazon SQS as the event buffer
[Section titled “Amazon SQS as the event buffer”](#amazon-sqs-as-the-event-buffer)
Amazon SQS provides a durable, decoupled messaging layer between S3 and Lambda. When S3 emits an `ObjectCreated` event, the message is placed onto the main queue, where it awaits processing.
The use of SQS ensures that transient failures in downstream services do not result in lost events: messages remain in the queue until successfully processed, and failed messages can be retried automatically. A Dead Letter Queue (DLQ) is configured alongside the main queue to capture messages that cannot be processed after repeated attempts, enabling operational visibility and troubleshooting.
The queue also enforces controlled consumption by Lambda, allowing the system to handle burst uploads from MASV without overwhelming downstream services.
## AWS Lambda processing and logic
[Section titled “AWS Lambda processing and logic”](#aws-lambda-processing-and-logic)
The AWS Lambda function acts as the orchestration layer that bridges AWS events with the LucidLink API. It is triggered by messages arriving in the SQS queue, typically processing one message per invocation to maintain simplicity and traceability.
Upon execution, the function parses the SQS payload to extract the underlying S3 event. It identifies the bucket and object key associated with the newly uploaded file and uses this information to construct a request to the LucidLink Connect API.
The Lambda function is configured with critical environment variables that define its behavior. These include the base URL of the LucidLink API, identifiers for the target datastore and Filespace, and parameters controlling how file paths are mapped. For example, path preservation ensures that the folder structure in S3 is reflected accurately within LucidLink, while directory creation flags ensure that missing paths are created automatically.
From a networking perspective, the Lambda function is deployed within the same VPC as the ECS service hosting the LucidLink API. This allows it to resolve and communicate with the API endpoint using an internal DNS name provided by AWS Cloud Map. The Lambda execution role must also include permissions for SQS consumption, CloudWatch logging, and VPC networking operations such as elastic network interface management.
## ECS and LucidLink API container
[Section titled “ECS and LucidLink API container”](#ecs-and-lucidlink-api-container)
The LucidLink Connect API is deployed as a containerized service within Amazon ECS using AWS Fargate. This service is responsible for receiving API calls from the Lambda function and registering files within a LucidLink Filespace.
The ECS service runs a task based on a task definition that specifies the container image, resource allocation, and networking configuration. The container exposes an HTTP endpoint on port 3003, which is used by the Lambda function to submit file registration requests.
To enable internal service discovery, the ECS service is integrated with AWS Cloud Map. This allows the service to be addressed via a stable DNS name within the VPC, eliminating the need to manage dynamic IP addresses. The configuration defines a namespace and service name, resulting in an endpoint such as `lucidlink-api.masv-aws`, which is referenced by the Lambda function and stored in environment variables.
Security groups applied to the ECS service restrict inbound traffic to internal VPC sources on the required port, while outbound traffic is permitted for HTTPS communication. This ensures that the API is not publicly accessible while still allowing it to interact with external services if required.
## Networking and security model
[Section titled “Networking and security model”](#networking-and-security-model)
A key design principle of this architecture is that all service-to-service communication occurs within a private VPC. The LucidLink API is not exposed to the public internet; instead, it is accessed internally by the Lambda function using private DNS resolution.
The Lambda function is configured with subnet and security group settings that allow it to communicate freely within the VPC. Its security group typically permits all outbound traffic, while the ECS service security group restricts inbound traffic to trusted internal sources and the specific API port.
This approach minimizes the attack surface and aligns with enterprise security expectations by avoiding public endpoints and enforcing least-privilege access patterns.
## End-to-end integration with MASV
[Section titled “End-to-end integration with MASV”](#end-to-end-integration-with-masv)
MASV serves as the entry point for all file uploads in this architecture. Through its [S3 cloud connection](/api/cloud-connections/) capability, MASV writes uploaded content directly into the configured S3 bucket. This eliminates the need for intermediate storage or transfer services within AWS.
After the file is written, the rest of the pipeline is entirely automated. S3 generates the event, SQS buffers it, Lambda processes it, and the LucidLink API registers it. The result is that files uploaded via MASV appear in LucidLink with minimal latency, backed by S3 storage.
This integration allows organizations to combine MASV’s high-performance file transfer capabilities with LucidLink’s real-time file access model, using AWS as the orchestration layer.
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
Before you deploy this architecture, ensure you have:
* An Amazon S3 bucket designated for hosting objects ingested from MASV [Portals](/api/portals/) and accessible to LucidLink users
* A MASV Portal with the appropriate Amazon S3 [cloud connection](/api/cloud-connections/) linking to the S3 bucket
* An AWS VPC to contain the networked components
For related information on connecting MASV with LucidLink, visit [How to connect MASV with LucidLink](https://help.massive.io/en/how-to-connect-masv-with-lucidlink).
## Get started
[Section titled “Get started”](#get-started)
Ready to deploy this integration? Set up the S3 bucket, SQS queue, Lambda function, and ECS-hosted LucidLink Connect API using the architecture described above. After deployment, files uploaded through your MASV Portal automatically appear in your LucidLink Filespace with near real-time availability.
To begin, configure your [MASV Portal with an S3 cloud connection](/api/cloud-connections/) and provision the AWS infrastructure components. The modular design lets you scale each component independently as your ingestion volume grows.
# Tutorials
> Step-by-step tutorials for common MASV integration tasks using the API, Agent, and Web Uploader SDK.
These tutorials walk you through real-world integration tasks with MASV. Each one is self-contained and links to the relevant reference pages for deeper detail.
## Available tutorials
[Section titled “Available tutorials”](#available-tutorials)
* **[Upload with Web Uploader](/tutorials/upload-web-uploader/)** — Send a file from the browser using the Web Uploader SDK.
* **[Upload with Agent (Docker)](/tutorials/upload-agent-docker/)** — Send a file using the MASV Agent running in Docker.
* **[Add Users to Teamspaces](/tutorials/add-users-teamspaces/)** — Manage Teamspace membership using the MASV API.
# Add users to Teamspaces
> Use the MASV API to list Team members, create a Teamspace, and add users to it step by step.
In this tutorial, you’ll learn how to add a Team member to a Teamspace using the MASV API.
A [Teamspace](https://help.massive.io/en/what-is-a-masv-teamspace) organizes a subset of Team members into a group to control access to specific content and functionality. It also provides an organizational tool to track activity for specific projects, clients, or departments.
For the full Teamspaces API specification, see the [Teamspaces reference](/api/teamspaces/).
## What you’ll learn
[Section titled “What you’ll learn”](#what-youll-learn)
This tutorial walks through the following steps:
1. [Get a list of Team members](#step-1-get-a-list-of-team-members)
2. [Get a list of Teamspaces](#step-2-get-a-list-of-teamspaces)
3. [Add a user to the Team](#step-3-add-a-user-to-the-team)
4. [Create a new Teamspace](#step-4-create-a-new-teamspace)
5. [Add a user to the Teamspace](#step-5-add-a-user-to-the-new-teamspace)
## Before you begin
[Section titled “Before you begin”](#before-you-begin)
You’ll need:
* A MASV account with the Owner or Admin [role](https://help.massive.io/en/what-are-the-permissions-for-owner-admins-and-members-of-your-team) — [sign up here](https://app.massive.io/en/signup)
* A [MASV API key](/api/api-keys/) — see [How to create and manage API keys](https://help.massive.io/en/how-to-create-and-manage-api-keys-in-masv)
All API requests in this tutorial use the base URI `https://api.massive.app`.
## Step 1: Get a list of Team members
[Section titled “Step 1: Get a list of Team members”](#step-1-get-a-list-of-team-members)
List all users who belong to a particular MASV Team.
| Method | Route |
| :----- | :---------------------------- |
| `GET` | `/v1/teams/{team_id}/members` |
Required headers:
| Name | Type | Required | Description |
| :---------- | :----- | :------- | :----------------------------- |
| `X-API-KEY` | String | Yes | Your [API key](/api/api-keys/) |
URL parameters:
| Name | Type | Required | Description |
| :-------- | :----- | :------- | :------------- |
| `team_id` | String | Yes | ID of the Team |
Locate and copy the `team_id` from the MASV Web App URL. For detailed instructions, see [How to find your Team ID and Portal ID](https://help.massive.io/en/how-can-i-find-my-team-id-and-portal-id).
Example request:
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -H "X-API-KEY: $API_KEY" \
-X GET https://api.massive.app/v1/teams/$TEAM_ID/members
```
Example response:
```json
[
{
"approved": true,
"email": "user@example.com",
"id": "string",
"invitation_accepted": "2026-02-05T17:32:10.870Z",
"name": "string",
"policy_key": "member",
"team_id": "string",
"teamspaces": [
{
"id": "string",
"name": "string"
}
],
"user_id": "string"
}
]
```
For the full response schema and error codes, see the [Teamspaces reference](/api/teamspaces/).
## Step 2: Get a list of Teamspaces
[Section titled “Step 2: Get a list of Teamspaces”](#step-2-get-a-list-of-teamspaces)
Authorized users can list all existing Teamspaces for any Team they belong to (subject to [access policy](https://help.massive.io/en/what-are-the-permissions-for-owner-admins-and-members-of-your-team)).
| Method | Route |
| :----- | :----------------------------- |
| `GET` | `/v1.1/teams/{team_id}/spaces` |
Required headers:
| Name | Type | Required | Description |
| :---------- | :----- | :------- | :----------------------------- |
| `X-API-KEY` | String | Yes | Your [API key](/api/api-keys/) |
URL parameters:
| Name | Type | Required | Description |
| :-------- | :----- | :------- | :------------------------------------- |
| `team_id` | String | Yes | ID of the Team that owns the Teamspace |
Query parameters:
| Name | Type | Required | Description |
| :------ | :------ | :------- | :----------------------------------------------- |
| `page` | Integer | No | Page number. Default: `0` |
| `limit` | Integer | No | Maximum records to fetch (50 max). Default: `50` |
Example request:
```bash
curl -H "X-API-KEY: $API_KEY" \
-X GET https://api.massive.app/v1.1/teams/$TEAM_ID/spaces
```
Example response:
```json
{
"metadata": {
"per_page": 50,
"total": 1
},
"records": [
{
"id": "string",
"name": "string"
}
]
}
```
## Step 3: Add a user to the Team
[Section titled “Step 3: Add a user to the Team”](#step-3-add-a-user-to-the-team)
To invite a user to the Team, sign in to the MASV Web App as a Team Owner or Admin. Team invitations are managed through the MASV Web App — there is no direct API endpoint for this step. For each new user, assign a [role](https://help.massive.io/en/what-are-the-permissions-for-owner-admins-and-members-of-your-team) to determine their permissions. For this tutorial, select **Member** as the role.
1. From the sidebar, select **Features & Settings**, then **User Management**.
2. On the **User Management** page, select **+ Add Users** in the upper right corner.
3. In the **Add Users** dialog, enter the email address for the user you want to invite.
4. From the **Role** dropdown, select **Member**.
5. Select **Add**. Repeat to invite additional users.
6. Select **Send** when you’ve added all users.
The system sends an email invitation to each invited user. Invited users appear as **Pending** in the MASV Web App until they accept the invitation.
## Step 4: Create a new Teamspace
[Section titled “Step 4: Create a new Teamspace”](#step-4-create-a-new-teamspace)
Create a Teamspace under a Team. You can optionally provide `membership_ids` for existing Team members when creating the Teamspace, but for this tutorial you’ll create it first and add users afterward.
| Method | Route |
| :----- | :--------------------------- |
| `POST` | `/v1/teams/{team_id}/spaces` |
Required headers:
| Name | Type | Required | Description |
| :------------- | :----- | :------- | :----------------------------- |
| `X-API-KEY` | String | Yes | Your [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
URL parameters:
| Name | Type | Required | Description |
| :-------- | :----- | :------- | :------------- |
| `team_id` | String | Yes | ID of the Team |
Request body:
| Name | Type | Required | Description |
| :--------------- | :-------- | :------- | :------------------------------------ |
| `name` | String | Yes | Name of the Teamspace to create |
| `membership_ids` | String\[] | No | Membership IDs of Team members to add |
Example request:
```bash
curl -d '{"name": "Marketing"}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/teams/$TEAM_ID/spaces
```
Example response:
```json
{
"id": "01E8TP2TJCTDNW11G67NKHQW5J",
"members": [],
"name": "Marketing"
}
```
For the full request and response schema, see [Create Teamspace](/api/teamspaces/#create-teamspace) in the Teamspaces reference.
## Step 5: Add a user to the new Teamspace
[Section titled “Step 5: Add a user to the new Teamspace”](#step-5-add-a-user-to-the-new-teamspace)
Team Owners and Admins already have read and write access to all Teamspaces. Other Team members need to be explicitly added. A Team member can belong to multiple Teamspaces.
| Method | Route |
| :----- | :------------------------------ |
| `POST` | `/v1/spaces/{space_id}/members` |
Required headers:
| Name | Type | Required | Description |
| :------------- | :----- | :------- | :----------------------------- |
| `X-API-KEY` | String | Yes | Your [API key](/api/api-keys/) |
| `Content-Type` | String | Yes | Must be `application/json` |
URL parameters:
| Name | Type | Required | Description |
| :--------- | :----- | :------- | :-------------------------------- |
| `space_id` | String | Yes | ID of the Teamspace (from Step 4) |
Request body:
| Name | Type | Required | Description |
| :--------------- | :-------- | :------- | :------------------------------------------ |
| `membership_ids` | String\[] | Yes | Team membership IDs to add to the Teamspace |
First, repeat [Step 1](#step-1-get-a-list-of-team-members) to find the newly added Team member’s `id` (membership ID). For example:
```json
{
"email": "newuser@example.com",
"id": "01KGJN265PMVBG59EK6DXVHZQ5",
"name": "Alex Kim",
"policy_key": "member",
"team_id": "01KESZAJ7NNWAQ9TAW3R4Q27CB",
"user_id": "01KGJN265HXMS0D1NWJFC5TS7J"
}
```
Use the Teamspace ID from Step 4 in the path and the member’s `id` in the body.
Example request:
```bash
curl -d '{"membership_ids": ["01KGJN265PMVBG59EK6DXVHZQ5"]}' \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-X POST https://api.massive.app/v1/spaces/$SPACE_ID/members
```
Example response:
```json
[
{
"email": "newuser@example.com",
"id": "01KGJN265PMVBG59EK6DXVHZQ5",
"name": "Alex Kim",
"policy_key": "member",
"team_id": "01KESZAJ7NNWAQ9TAW3R4Q27CB",
"teamspaces": [
{
"id": "01E8TP2TJCTDNW11G67NKHQW5J",
"name": "Marketing"
}
],
"user_id": "01KGJN265HXMS0D1NWJFC5TS7J"
}
]
```
For the full endpoint specification, see [Add Team members to Teamspace](/api/teamspaces/#add-team-members-to-teamspace) in the Teamspaces reference.
## Summary
[Section titled “Summary”](#summary)
You’ve learned how to use the MASV API to:
* List Team members and Teamspaces
* Invite a user to a Team via the MASV Web App
* Create a new Teamspace
* Add a Team member to a Teamspace
## Next steps
[Section titled “Next steps”](#next-steps)
* **[Teamspaces reference](/api/teamspaces/)** — Full API specification for Teamspace operations.
* **[API Keys](/api/api-keys/)** — Manage API keys for authentication.
* **[Core Concepts](/api/core-concepts/)** — Understand Teams, Portals, Packages, and Teamspaces.
* **[MASV Teamspaces in the Web App](https://help.massive.io/en/what-is-a-masv-teamspace)** — Manage Teamspaces through the MASV Web App UI.
# MASV Agent-based automation
> Tutorial on how to automate transfers with the MASV Agent, a cross-platform headless server.
In this tutorial, you’ll use the MASV Agent, a cross-platform headless service that runs on your server, workstation, or NAS device, to automate transfers. The MASV Agent manages upload and download transfers locally, handling chunking, retries, and automatic transfer resume. You control it through a CLI or its local REST API — making it easy to execute from shell scripts, orchestration tools, or any language that can make HTTP calls to localhost.
## What you’ll learn
[Section titled “What you’ll learn”](#what-youll-learn)
In this tutorial, you will:
* Start the MASV Agent
* Authenticate
* Send files to a portal
* Monitor transfer status
* Manage in-progress transfers
* Download files
## Architecture overview
[Section titled “Architecture overview”](#architecture-overview)
```text
[Your server / NAS / pipeline host]
│
│ MASV Agent runs as a persistent local service.
│
├── masv server start --api-key="$MASV_API_KEY"
│
│ Your application or script issues commands:
│ via CLI: masv upload start portal ...
│ via local REST: POST http://localhost:8080/api/v1/portals/uploads
│
[MASV Cloud]
│
│ Transfer completes; webhook is triggered (optional).
│
[Downstream system]
```
## Before you begin
[Section titled “Before you begin”](#before-you-begin)
* [Install MASV Agent](/agent/getting-started/#installation) on your host. It is available as a native binary for Windows, macOS, and Linux, or as a Docker container image.
* [Generate an API key](https://help.massive.io/en/how-to-create-and-manage-api-keys-in-masv) in the MASV Web App.
* Identify the portals or individuals/team you want to send to.
## Step 1: Start the MASV Agent server
[Section titled “Step 1: Start the MASV Agent server”](#step-1-start-the-masv-agent-server)
Start the MASV Agent’s local server, providing your API key at startup:
```bash
# Store your API key in an environment variable — never hardcode it.
export MASV_API_KEY="your-key-here"
masv server start --api-key="$MASV_API_KEY"
```
The server starts listening at `http://localhost:8080` by default. All subsequent MASV Agent commands communicate with this local server.
Useful startup flags:
| Flag | Purpose |
| :------------------------------- | :------------------------------------------------------------------------------- |
| `--auto-finalize=true` | Automatically finalizes uploads after all files are transferred (default: true). |
| `--auto-resume=true` | Resumes in-progress transfers after a restart (default: true). |
| `--chunk-size=100MB` | Sets the default chunk size for all transfers. |
| `--listen=http://localhost:8080` | Changes the listen address if the default port is unavailable. |
For production deployments, configure the MASV Agent to start on system boot using your platform’s service manager (systemd, launchd, or Windows Services).
## Step 2: Authenticate (for team uploads)
[Section titled “Step 2: Authenticate (for team uploads)”](#step-2-authenticate-for-team-uploads)
Portal uploads do not require a user session — you can send uploads to any portal without logging in. Individual and team uploads (packages sent to email recipients or shared via a link) require a user session:
```bash
masv user login --email "$MASV_EMAIL" --password "$MASV_PASSWORD"
```
For automation, pass the API key at server start instead (recommended):
```bash
masv server start --api-key="$MASV_API_KEY"
```
## Step 3: Send files to a portal
[Section titled “Step 3: Send files to a portal”](#step-3-send-files-to-a-portal)
Using the CLI:
```bash
masv upload start portal \
--subdomain=your-portal-subdomain \
--sender='pipeline@yourcompany.com' \
--name="Dailies - 2026-06-10" \
--description="Camera A rushes" \
/mnt/storage/project/dailies/
```
Using the local REST API:
```bash
curl -X POST \
-H "Content-Type: application/json" \
http://localhost:8080/api/v1/portals/uploads \
-d '{
"subdomain": "your-portal-subdomain",
"sender_email": "pipeline@yourcompany.com",
"package_name": "Dailies - 2026-06-10",
"paths": ["/mnt/storage/project/dailies/"],
"package_description": "Camera A rushes"
}'
```
The response includes an upload ID you can use to monitor progress.
## Step 4: Monitor transfer status
[Section titled “Step 4: Monitor transfer status”](#step-4-monitor-transfer-status)
Using the CLI:
```bash
masv upload ls # list all uploads
masv upload status UPLOAD_ID # full details including per-file state
```
Using the local REST API:
```bash
curl http://localhost:8080/api/v1/uploads/{UPLOAD_ID}
```
Upload states to watch for:
| State | Meaning |
| :------------- | :----------------------------------------- |
| `transferring` | Files are actively being sent. |
| `idle` | All bytes uploaded; awaiting finalization. |
| `complete` | Package finalized and recipient notified. |
| `error` | A fatal error occurred. |
| `paused` | Transfer was paused. |
## Step 5: Manage in-progress transfers
[Section titled “Step 5: Manage in-progress transfers”](#step-5-manage-in-progress-transfers)
You can pause, resume, or cancel transfers at any time:
```bash
masv upload pause UPLOAD_ID
masv upload resume UPLOAD_ID
masv upload rm UPLOAD_ID
```
## Step 6: Download files (for receive workflows)
[Section titled “Step 6: Download files (for receive workflows)”](#step-6-download-files-for-receive-workflows)
MASV Agent also handles downloads, making it straightforward to build automation around receiving file packages:
```bash
# Start a download from a download link
masv download start LINK_ID --secret LINK_SECRET --destination /mnt/ingest/
```
Or via local REST:
```bash
curl -X POST \
-H "Content-Type: application/json" \
http://localhost:8080/api/v1/downloads \
-d '{
"link_id": "LINK_ID",
"secret": "LINK_SECRET",
"destination_path": "/mnt/ingest/"
}'
```
## Production considerations
[Section titled “Production considerations”](#production-considerations)
* **Auto-finalize behavior:** By default, the MASV Agent finalizes uploads automatically when all bytes are transferred. If your workflow includes adding files to a package incrementally — for example, a render job that produces files over time — disable auto-finalize at startup (`--auto-finalize=false`) and finalize explicitly when the package is ready.
* **State persistence:** The MASV Agent stores transfer state locally in `$HOME/.masvsrv`. If you redeploy the MASV Agent or move it to a new host, transfer history does not carry over. Use `--config-dir` to point to persistent storage if needed.
* **Docker deployments:** The MASV Agent is available as a container image. When running in Docker, ensure the paths you pass to the MASV Agent are paths inside the container’s filesystem, and mount your host storage as volumes accordingly.
* **Storage Gateway:** For NAS and on-premises storage environments, MASV Agent supports a Storage Gateway mode that exposes your connected storage to MASV Portals, enabling automatic file delivery to physical storage without requiring your team to have direct network access to the device.
# Direct MASV API integration
> Tutorial on how to use the MASV REST API to communicate directly with your application.
In this tutorial, you’ll use the MASV REST API to communicate directly with your application, without using the MASV Agent or Web Uploader SDK. Your code implements the full upload lifecycle: creating packages, registering files, obtaining pre-signed upload URLs, uploading chunks to cloud storage, finalizing files, and finalizing the package.
Caution
For most use cases, the Web Uploader or MASV Agent provide the same control with far less implementation complexity. Choose a direct API integration only when neither of those options fit. Contact for further guidance.
## What you’ll learn
[Section titled “What you’ll learn”](#what-youll-learn)
In this tutorial, using the MASV API, you will:
* Create a package
* Register a file with the package
* Initiate a multipart upload to cloud storage
* Obtain pre-signed URLs for each file chunk
* Upload file chunks
* Finalize a file
* Finalize a package
## Architecture overview
[Section titled “Architecture overview”](#architecture-overview)
```text
[Your application]
│
│ All API calls authenticated with X-API-KEY header
│
├── 1. Create package POST /teams/{team_id}/packages
│ POST /portals/{portal_id}/packages
│
│ Package returns: id, access_token (package token)
│ All subsequent file operations use X-Package-Token header.
│
├── For each file:
│ ├── 2. Register file POST /packages/{pkg_id}/files
│ ├── 3. Create in storage Follow create_blueprint (S3 multipart initiation)
│ ├── 4. Get chunk URLs POST /packages/{pkg_id}/files/{file_id}?start=0&count=N
│ ├── 5. Upload chunks PUT directly to pre-signed S3 URLs
│ ├── 6. Finalize file POST /packages/{pkg_id}/files/{file_id}/finalize
│
└── 7. Finalize package POST /packages/{pkg_id}/finalize
```
## Before you begin
[Section titled “Before you begin”](#before-you-begin)
Obtain your Team ID. You can retrieve it using:
```bash
curl -H "X-API-KEY: $API_KEY" \
-X GET https://api.massive.app/v1/teams
```
Decide whether you are uploading to an individual/team (sending to email recipients or creating a link) or to a portal (depositing into a designated collection point).
## Step 1: Create a package
[Section titled “Step 1: Create a package”](#step-1-create-a-package)
All uploaded files must belong to a package. Create one for your team or a portal:
Team package:
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -X POST "https://api.massive.app/v1/teams/{team_id}/packages" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Render Output 2026-06-10",
"description": "Final composited frames for episode 3",
"recipients": ["editor@studio.com"],
"chunk_size": 104857600
}'
```
Portal package:
```bash
curl -X POST "https://api.massive.app/v1/portals/{portal_id}/packages" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Render Output 2026-06-10",
"description": "Final composited frames for episode 3",
"sender": "render@yourpipeline.com",
"chunk_size": 104857600
}'
```
The response contains `id` (the package ID) and `access_token` (the package token). Store both — `id` is used in all subsequent URLs, and `access_token` is passed as the `X-Package-Token` header for all file operations.
**Set `chunk_size` explicitly.** Although not required, providing it ensures compatibility and consistent performance. The default is 100 MiB (104857600 bytes). For files approaching 1 TB, you will need to increase this value to stay within the 10,000-chunk limit imposed by the underlying storage service.
## Step 2: Register a file with the package
[Section titled “Step 2: Register a file with the package”](#step-2-register-a-file-with-the-package)
For each file you want to upload, register it with the package. This tells MASV about the file and returns the blueprint you will use to initiate the multipart upload in cloud storage.
```bash
curl -X POST "https://api.massive.app/v1/packages/{package_id}/files" \
-H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"kind": "file",
"name": "frame_0001.exr",
"path": "renders/ep3/",
"last_modified": "2026-06-10T09:00:00.000Z",
"size": 220200960
}'
```
The response includes a `create_blueprint` object and a `file.id` you will need in later steps. The `create_blueprint` contains the method, URL, and headers for creating the file in cloud storage.
## Step 3: Initiate the multipart upload in cloud storage
[Section titled “Step 3: Initiate the multipart upload in cloud storage”](#step-3-initiate-the-multipart-upload-in-cloud-storage)
Execute the HTTP request described by `create_blueprint` exactly as specified:
```text
{method}: {url}
Headers: {headers from blueprint}
```
This initiates a multipart upload in MASV’s cloud storage (typically Amazon S3). The response is XML and contains an `UploadId` — store it, as it is required for all subsequent chunk operations for this file.
## Step 4: Obtain pre-signed URLs for each file chunk
[Section titled “Step 4: Obtain pre-signed URLs for each file chunk”](#step-4-obtain-pre-signed-urls-for-each-file-chunk)
Request upload URLs for the chunks you are about to send. You can request them in bulk:
```bash
curl -X POST \
"https://api.massive.app/v1/packages/{package_id}/files/{file_id}?start=0&count=3" \
-H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"upload_id": "THE_UPLOAD_ID_FROM_STEP_3"
}'
```
The response is an ordered array of blueprints — one per chunk — each containing a `method` and `url` (and sometimes additional headers).
**Request only as many URLs as you need.** Over-requesting causes unnecessary overhead. For large files or streaming scenarios where the total size is not known up front, request URLs in smaller batches as you consume them.
**URLs expire.** Pre-signed upload URLs are time-limited. If a URL expires before you use it, request a fresh set for the remaining chunks.
## Step 5: Upload file chunks
[Section titled “Step 5: Upload file chunks”](#step-5-upload-file-chunks)
Upload each chunk using its corresponding blueprint. Chunks are uploaded directly to MASV’s cloud storage — not to the MASV API server.
```text
PUT {url from blueprint}
Headers: {any headers specified in the blueprint}
Body: the raw bytes for this chunk
```
From the response headers, capture:
* `ETag` — the hash of the chunk as confirmed by cloud storage.
Along with the chunk’s `partNumber` (its 1-based index), you will need both values to finalize the file.
**Include all blueprint headers.** If the blueprint specifies headers, they must be included in your PUT request exactly as provided, or the request will fail.
## Step 6: Finalize the file
[Section titled “Step 6: Finalize the file”](#step-6-finalize-the-file)
After all chunks for a file are uploaded, tell MASV that the file is complete:
```bash
curl -X POST \
"https://api.massive.app/v1/packages/{package_id}/files/{file_id}/finalize" \
-H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"size": 220200960,
"chunk_size": 104857600,
"file_extras": {
"upload_id": "THE_UPLOAD_ID_FROM_STEP_3"
},
"chunk_extras": [
{ "part_number": "1", "etag": "\"abc123...\"" },
{ "part_number": "2", "etag": "\"def456...\"" },
{ "part_number": "3", "etag": "\"ghi789...\"" }
]
}'
```
A `204 No Content` response means the file was successfully sealed in storage. Repeat steps 2–6 for each file in the package.
**If the submitted `size` does not match the actual object size in storage, MASV responds with `409 Conflict`.** Always provide the exact byte count.
## Step 7: Finalize the package
[Section titled “Step 7: Finalize the package”](#step-7-finalize-the-package)
After all files are finalized, close the package. This triggers delivery to recipients and starts the expiry clock.
```bash
curl -X POST \
"https://api.massive.app/v1/packages/{package_id}/finalize" \
-H "X-Package-Token: $PACKAGE_TOKEN" \
-H "Content-Type: application/json"
```
A `204 No Content` response means the package is dispatched.
**Finalize files before finalizing the package.** Any files that are not yet finalized at the time of package finalization are dropped and will not be delivered. If you are uploading files concurrently, wait for all file finalizations to complete before finalizing the package.
## Production considerations
[Section titled “Production considerations”](#production-considerations)
* **Implement retries.** Network interruptions, expired pre-signed URLs, and transient 5xx errors are normal. Design your upload loop to detect failures, re-request chunk URLs if needed, and retry failed chunks with exponential back-off.
* **Track chunk metadata.** Your application needs to store `partNumber` and `ETag` for each successfully uploaded chunk to finalize the file. If your process crashes mid-upload, you may need to restart the upload for that file from the beginning unless you persist this metadata.
* **Concurrency.** You can upload multiple chunks concurrently for a single file, and you can upload multiple files concurrently within the same package. Tune concurrency carefully according to your network capacity.
* **Chunk size for large files.** For files approaching 1 TB, verify that `file_size / chunk_size` does not exceed the 10,000-chunk limit (the S3 limit). Fetch the system spec endpoint to retrieve current limits:
```bash
curl -H "X-API-KEY: $API_KEY" \
https://api.massive.app/v1/system/packages/spec
```
# Configure Portal webhooks
> Set up a webhook to receive real-time notifications when files are uploaded to your MASV Portal.
Webhooks let your application react to events in real time. Instead of polling the API for new packages, you register a URL and MASV sends an HTTP request whenever a specified event occurs. In this tutorial, you configure a webhook that fires when a package is uploaded to your portal.
## What you’ll learn
[Section titled “What you’ll learn”](#what-youll-learn)
* Create a webhook subscribed to portal package events
* Attach the webhook to a portal
* Parse the webhook payload and extract package information
* Secure your webhook endpoint
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
* A MASV API key. Store it in the `API_KEY` environment variable.
* A portal configured in your Team.
* An HTTPS endpoint that can receive POST requests (use [webhook.site](https://webhook.site) for testing).
* Your Team ID and portal ID stored in `TEAM_ID` and `PORTAL_ID` environment variables.
## Step 1: Create a webhook
[Section titled “Step 1: Create a webhook”](#step-1-create-a-webhook)
Register a webhook endpoint and subscribe it to `package.finalized` events:
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -X POST "https://api.massive.app/v1/teams/$TEAM_ID/custom_webhooks" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d "{
\"active\": true,
\"method\": \"POST\",
\"name\": \"Portal Upload Notifications\",
\"url\": \"https://your-server.example.com/webhooks/masv\",
\"events\": [\"package.finalized\"],
\"headers\": {\"X-Webhook-Secret\": \"$WEBHOOK_SECRET\"}
}"
```
The `headers` field lets you include a shared secret that your endpoint can validate on every incoming request. This is the primary mechanism for verifying that requests originate from MASV.
A successful response returns `201 Created` with the webhook object:
```json
{
"id": "",
"active": true,
"method": "POST",
"name": "Portal Upload Notifications",
"url": "https://your-server.example.com/webhooks/masv",
"events": ["package.finalized"],
"headers": {"X-Webhook-Secret": ""}
}
```
Tip
Store your shared secret in a secrets manager or environment variable. Treat it with the same care as your API key.
## Step 2: Attach the webhook to a portal
[Section titled “Step 2: Attach the webhook to a portal”](#step-2-attach-the-webhook-to-a-portal)
Webhooks must be attached to a portal to receive events from it. Update your portal to include the webhook:
Note
When updating a portal, provide the full portal object in the request body. The example below shows only the `custom_webhooks` field for clarity. Include all existing portal fields (such as `name`, `subdomain`, `active`, `has_access_code`) to avoid overwriting them.
```bash
curl -X PUT "https://api.massive.app/v1/portals/$PORTAL_ID" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "...",
"subdomain": "...",
"active": true,
"custom_webhooks": [{"id": "$WEBHOOK_ID"}]
}'
```
Caution
Provide the full list of attached webhooks on every PUT request. Any webhook IDs omitted from the `custom_webhooks` array are detached.
## Step 3: Test the webhook
[Section titled “Step 3: Test the webhook”](#step-3-test-the-webhook)
Upload a file to your portal to trigger the webhook. Use the MASV web interface, the Agent CLI, or the API.
After the upload finalizes, MASV sends a POST request to your registered URL. The payload looks like this:
```json
{
"event_id": "",
"event_time": "2026-05-04T12:05:30.000Z",
"event_type": "package.finalized",
"body_extras": {},
"object": {
"type": "package",
"id": "",
"name": "project-assets",
"portal_id": "",
"sender": "sender@example.com",
"size": 524288000,
"state": "finalized",
"total_files": 3,
"created_at": "2026-05-04T12:00:00.000Z",
"updated_at": "2026-05-04T12:05:30.000Z"
},
"custom_webhook_id": ""
}
```
Confirm your endpoint received the request and returned a `200` status code. MASV retries failed deliveries up to 5 times with incremental backoff.
## Step 4: Secure your endpoint
[Section titled “Step 4: Secure your endpoint”](#step-4-secure-your-endpoint)
MASV does not include a cryptographic signature on webhook payloads. Use the custom `headers` field (set in Step 1) to verify requests:
```python
import os
import hmac
from flask import Flask, request, abort
app = Flask(__name__)
WEBHOOK_SECRET = os.environ["MASV_WEBHOOK_SECRET"]
@app.route("/webhooks/masv", methods=["POST"])
def handle_webhook():
# Verify the shared secret header
received_secret = request.headers.get("X-Webhook-Secret", "")
if not hmac.compare_digest(received_secret, WEBHOOK_SECRET):
abort(401)
event = request.get_json()
if event["event_type"] == "package.finalized":
package_id = event["object"]["id"]
sender = event["object"]["sender"]
size = event["object"]["size"]
print(f"New package {package_id} from {sender} ({size} bytes)")
# Trigger your downstream workflow here
return "", 200
```
Caution
Always validate the shared secret header before processing the payload. Without validation, any party that discovers your endpoint URL can send fake events.
Return a `200` response promptly. If your processing takes time, acknowledge the webhook immediately and handle the work asynchronously.
## Next steps
[Section titled “Next steps”](#next-steps)
* **[Webhooks reference](/api/webhooks/)** — Full list of supported events and configuration options.
* **[Cloud Connections](/api/cloud-connections/)** — Automatically route uploaded files to external storage.
* **[Automations](/agent/automations/)** — Set up Agent-based automated download workflows.
# Embed the MASV uploader
> Tutorial on how to embed the MASV uploader into your application.
In this tutorial, you’ll use your application for the upload UI — the file picker, the progress bar, the branding — while MASV handles all the complexity of large file transfer underneath. The Web Uploader SDK (`@masvio/uploader`) is the data plane for this pattern. Your back end is responsible for orchestrating the session: creating the portal, creating the package, issuing credentials to the client, and reacting to completed transfers.
## What you’ll learn
[Section titled “What you’ll learn”](#what-youll-learn)
In this tutorial, you will:
* Resolve a portal
* Create a package
* Initialize the uploader
* Collect files and start an upload
* Show upload progress
* React to the completed transfer
## Architecture overview
[Section titled “Architecture overview”](#architecture-overview)
```text
[User's browser]
│
│ 1. User initiates an upload action in your app.
│
[Your back end]
│
│ 2. Your server authenticates to MASV and creates a package.
│ 3. Your server returns the package ID + token to the front end.
│
[User's browser + MASV Web Uploader SDK]
│
│ 4. The SDK uses the package credentials to upload directly to MASV.
│ 5. Your app shows progress using SDK events.
│
[MASV Cloud]
│
│ 6. MASV triggers a webhook when the package is finalized.
│
[Your back end]
│
│ 7. Your back end reacts: triggers processing, sends notifications, etc.
```
The key security principle here is that your API key never leaves your server. The client receives only the short-lived package token, scoped to a single upload session.
## Before you begin
[Section titled “Before you begin”](#before-you-begin)
* Create a MASV account and generate an API key from the MASV Web App.
* Create a MASV Portal (if you’re not planning on sending packages directly to individual recipients or teams).
* Install the MASV Web Uploader SDK in your front-end project:
```bash
npm install @masvio/uploader
# or
yarn add @masvio/uploader
```
* Configure a webhook endpoint on your server and register it with your MASV Portal. Your server will receive `package.created` and `package.finalized` events.
## Step 1: Resolve the portal (back end)
[Section titled “Step 1: Resolve the portal (back end)”](#step-1-resolve-the-portal-back-end)
If you are uploading to a portal, you need its ID. Resolve it once at startup or on demand using the portal’s subdomain:
```text
GET https://api.massive.app/v1/subdomains/portals/{subdomain}
```
Store the returned `id` — this is the portal ID you will use when creating packages.
## Step 2: Create a package (back end)
[Section titled “Step 2: Create a package (back end)”](#step-2-create-a-package-back-end)
When a user begins an upload, your server creates a package on their behalf. This call requires your API key and should never be made from the browser.
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -X POST "https://api.massive.app/v1/portals/{portal_id}/packages" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Project Assets - June 2026",
"description": "Rushes from the Montréal shoot",
"sender": "user@yourdomain.com"
}'
```
The response contains two values you must hold onto:
* `id` — the package ID.
* `access_token` — the package token that authorizes write access to this specific package.
**Security note:** Return only the `id` and `access_token` to your front end. Your API key must remain server-side. The package token is short-lived and scoped to this upload — it cannot be used to access other packages or team resources.
## Step 3: Initialize the uploader (front end)
[Section titled “Step 3: Initialize the uploader (front end)”](#step-3-initialize-the-uploader-front-end)
In your browser code, import the SDK and initialize an uploader instance using the credentials your back end returned:
```javascript
import { Uploader } from '@masvio/uploader';
const uploader = new Uploader(packageId, packageToken, 'https://api.massive.app');
```
## Step 4: Collect files and start the upload (front end)
[Section titled “Step 4: Collect files and start the upload (front end)”](#step-4-collect-files-and-start-the-upload-front-end)
The Web Uploader does not provide a file picker UI. You build that yourself — typically a standard HTML input, a drag-and-drop zone, or a custom selection component.
After the user selects files, prepare them in the format the uploader expects:
```javascript
const filesToUpload = Array.from(fileInput.files).map(file => ({
id: crypto.randomUUID(), // a unique identifier you assign
file: file, // the browser File object
path: file.name // relative path used to preserve folder structure
}));
uploader.addFiles(filesToUpload);
```
As soon as files are added, uploading begins immediately. There is no separate start call required.
Unlike the MASV API or MASV Agent, you do not need to finalize the upload manually. The Web Uploader handles package finalization automatically after all files are uploaded.
## Step 5: Show progress (front end)
[Section titled “Step 5: Show progress (front end)”](#step-5-show-progress-front-end)
The Web Uploader emits events as the transfer progresses. Attach listeners to keep your UI in sync with the transfer state:
```javascript
uploader.on('progress', (completed, total, speed) => {
const percent = Math.round((completed / total) * 100);
progressBar.style.width = `${percent}%`;
speedDisplay.textContent = `${(speed.moving / 1e6).toFixed(1)} MB/s`;
});
uploader.on('complete', () => {
statusMessage.textContent = 'Upload complete.';
});
uploader.on('error', (err) => {
console.error('Upload failed:', err);
statusMessage.textContent = 'Upload failed. Please try again.';
});
```
## Step 6: React to the completed transfer (back end)
[Section titled “Step 6: React to the completed transfer (back end)”](#step-6-react-to-the-completed-transfer-back-end)
When MASV finalizes the package, it triggers a `package.finalized` webhook to your registered endpoint. The payload tells you the package ID, sender, total size, and number of files:
```json
{
"event_type": "package.finalized",
"object": {
"id": "JF4D076SA6FA1",
"name": "Project Assets - June 2026",
"portal_id": "01CYCWJC40RXPK3HNQVYKAX1K1",
"sender": "user@yourdomain.com",
"size": 33454545,
"state": "finalized",
"total_files": 12
}
}
```
Use this event to trigger whatever happens next in your system, such as moving files to storage, updating a database record, notifying a team member, or kicking off a processing job.
## Production considerations
[Section titled “Production considerations”](#production-considerations)
* **Retry handling:** The Web Uploader handles retries on upload failures internally. You do not need to implement retry logic in your application for the transfer itself.
* **Package token expiry:** Package tokens are short-lived. If your upload session spans a long user interaction before files are added, create the package closer to the moment the upload starts.
* **Webhook reliability:** MASV retries webhook delivery up to five times, with incremental backoff intervals. Make your webhook handler idempotent so duplicate deliveries do not cause side effects.
# Intro to platform integration
> Introduction to four different ways to integrate MASV into your apps and workflows.
This introduction is designed to help you understand different ways of integrating MASV into your applications and workflows — and how to choose between them.
## Four ways to integrate with MASV
[Section titled “Four ways to integrate with MASV”](#four-ways-to-integrate-with-masv)
MASV is a file transfer platform that offers multiple mechanisms to ingest content for upstream processing and to egress content for downstream workflow steps or users.
The integration methods described here are designed to be embedded into applications, workflows, and media/data pipelines.
Each of the integration patterns described below links to a corresponding tutorial.
### Pattern 1 — [MASV Portal for intake](/tutorials/portal-intake/)
[Section titled “Pattern 1 — MASV Portal for intake”](#pattern-1--masv-portal-for-intake)
For receiving files from external contributors, this is the quickest and easiest way to get started by leveraging MASV UI components. The tutorial includes:
* Hosted, embedded, and custom-UI portal variants.
* Portal creation and configuration via API.
* Cloud connections for auto-routing to Amazon S3, Google Cloud Storage, and other destinations.
* Webhook-driven processing and dynamic portal lifecycle management.
### Pattern 2 — [Embed MASV uploader](/tutorials/embed-uploader/)
[Section titled “Pattern 2 — Embed MASV uploader”](#pattern-2--embed-masv-uploader)
For applications that own the UI, this method provides a more white label style experience. The tutorial includes:
* How to configure the front-end/back-end split.
* MASV Web Uploader SDK setup.
* Package session creation, file collection, progress events, and webhook-based post-upload handling.
* Security framing (API key stays server-side; only the package token reaches the browser).
### Pattern 3 — [MASV Agent-based automation](/tutorials/agent-automation/)
[Section titled “Pattern 3 — MASV Agent-based automation”](#pattern-3--masv-agent-based-automation)
For servers, NAS devices, and pipelines, this method uses an installed agent and storage gateway. This tutorial includes:
* MASV Agent installation.
* Startup flags.
* Authentication for team vs. portal uploads.
* CLI and local REST API command pairs.
* Upload lifecycle states.
* Download automation.
* Storage Gateway context.
### Pattern 4 — [Direct MASV API integration](/tutorials/api-direct/)
[Section titled “Pattern 4 — Direct MASV API integration”](#pattern-4--direct-masv-api-integration)
For environments where the MASV Agent or Web Uploader SDK can’t be used.
Caution
To ensure optimal performance of a direct API integration, we recommend that you contact for guidance.
This tutorial includes:
* Full seven-step upload lifecycle — create package, register file, initiate multipart upload in cloud storage, get chunk URLs, upload chunks, finalize file, finalize package.
* The `create_blueprint` pattern.
* ETag tracking.
* Critical ordering warnings.
## Basic steps
[Section titled “Basic steps”](#basic-steps)
Whether you’re building a custom upload experience, automating content delivery into storage, or integrating MASV into an existing MAM or DAM system, every integration follows the same fundamental model:
1. Content enters MASV through a portal, the Web Uploader, the MASV Agent, or an API-driven workflow.
2. MASV creates and manages a package that represents a managed unit of transfer.
3. Metadata and workflow state are associated with the package.
4. Files are delivered to recipients, storage destinations, or downstream systems.
5. Events generated by the transfer lifecycle can trigger additional automation.
Understanding this model is the key to understanding the rest of the platform.
## How MASV is structured
[Section titled “How MASV is structured”](#how-masv-is-structured)
Before choosing an integration pattern, it helps to understand that MASV separates two distinct responsibilities:
**The control plane** is the MASV REST API. It manages your teams, portals, packages, links, and metadata. Every portal you create, every package you track, and every notification you configure are managed through the control plane. Your application server speaks to the control plane using an API key.
**The data plane** is responsible for moving the actual file bytes. This is handled by MASV’s transfer infrastructure — either through the MASV Agent, the Web Uploader SDK, or direct API upload calls. Files travel to and from MASV’s cloud storage infrastructure, chunked and accelerated, independent of the control plane.
In most integrations, your back end orchestrates the control plane while the MASV Agent or Uploader handles the data plane. Understanding this separation makes it much easier to determine which integration pattern fits your situation.
## How to choose an integration pattern
[Section titled “How to choose an integration pattern”](#how-to-choose-an-integration-pattern)
The best integration strategy depends on where workflow ownership resides.
* If your application owns the user experience, use the **Web Uploader** and **Web Downloader SDKs**.
* If external contributors need a managed submission experience, use MASV **Portals**.
* If your application owns workflow state and orchestration, integrate with the MASV RESTful **APIs**.
* If storage infrastructure is the source or destination of content, use **MASV Agent and Integrations**.
* When automation is required, use **Webhooks**.
Most production deployments eventually combine several of these patterns. A typical media workflow may use portals for inbound delivery, Cloud Connections for storage routing, Webhooks for automation, and the API for workflow orchestration. Because all these components ultimately operate on the same package lifecycle, they can be combined without introducing additional workflow models for developers to learn.
| Pattern | Best for | Data plane | Who initiates the transfer? |
| :---------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ | :------------------------------------ | :------------------------------------- |
| [MASV Portal for intake](/tutorials/portal-intake/) | Receiving files from external collaborators, clients, or contributors; a zero-install upload experience for senders | MASV-hosted portal or embedded portal | Your customer, client, or collaborator |
| [Embed MASV uploader](/tutorials/embed-uploader/) | Custom web applications with your own UI | Web Uploader SDK | End user in the browser |
| [MASV Agent-based automation](/tutorials/agent-automation/) | Servers, NAS, pipelines, headless environments | MASV Agent | Your application or scheduled process |
| [Direct MASV API integration](/tutorials/api-direct/) | Platforms where MASV Agent is unavailable; maximum control | Raw API calls | Your application |
## Combining patterns
[Section titled “Combining patterns”](#combining-patterns)
Real-world integrations often combine multiple patterns. Some common combinations:
**Inbound + outbound workflow:** Use portal for intake (Pattern 1) to receive files from clients, process them on your infrastructure using automation driven by the MASV Agent (Pattern 3), and send the output back to the client using a direct team package via the API (Pattern 4).
**SaaS with custom upload UI:** Build an embedded upload experience (Pattern 2) for your end users’ inbound workflow. Use Agent-based automation (Pattern 3) for back end to back end file movement, such as delivering processed results to storage systems or partner platforms.
**Dynamic portal provisioning:** Use the API (Pattern 4 control plane) to programmatically create, configure, and tear down portals (Pattern 1) as part of your project lifecycle management — for example, you can create a dedicated intake portal when a new project opens and delete it when the project closes.
# MASV Portal for intake
> Tutorial on how to set up a MASV Portal as a hosted upload page that can collect files from anyone.
In this tutorial, you’ll use a MASV Portal as a hosted upload page that can collect files from anyone — including people who do not have a MASV account.
## What you’ll learn
[Section titled “What you’ll learn”](#what-youll-learn)
In this tutorial, you will:
* Create and configure a portal through the MASV API.
* Connect storage.
* Register a webhook.
* Share the portal.
* Process incoming packages.
* Manage portals.
## Variants of this pattern
[Section titled “Variants of this pattern”](#variants-of-this-pattern)
There are two sub-approaches you can take, depending on how much UI ownership you want:
**Hosted portal (linked):** You point senders to `yourname.portal.massive.io`. MASV hosts the upload page entirely. You configure its appearance — logo, colors, background — via the API or MASV Web App. No front-end code is required on your side.
**Embedded portal (iframe):** MASV generates embed code that you include in your own web page. The portal renders inside your site but still runs entirely on MASV infrastructure.
**Custom intake with API + Web Uploader:** You build the upload UI yourself (Pattern 2) but configure it to submit packages to your portal. This gives you full UI control while still routing files through your portal’s configuration — webhooks, cloud connections, access controls, and custom forms.
## Before you begin
[Section titled “Before you begin”](#before-you-begin)
* Generate an API key.
* Decide whether senders will use the portal’s MASV-hosted URL, an embedded portal, or a fully custom UI.
* Plan what happens after a file arrives: Does it go to cloud storage? Trigger a workflow? Notify a team member?
## Step 1: Create and configure a MASV Portal
[Section titled “Step 1: Create and configure a MASV Portal”](#step-1-create-and-configure-a-masv-portal)
Create a portal using the API:
```bash
# Store your API key in an environment variable — never hardcode it.
# See /api/api-keys/ for key management best practices.
curl -X POST "https://api.massive.app/v1/teams/$TEAM_ID/portals" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Client Deliverables",
"subdomain": "client-deliverables",
"active": true,
"recipients": ["team@yourcompany.com"],
"message": "Please upload your project files here.",
"custom_expiry_days": 7
}'
```
Configure the portal according to your intake requirements. Key properties to consider:
| Property | Purpose |
| :--------------------------------------------- | :------------------------------------------------------------- |
| `has_access_code` / `access_code` | Restrict who can upload — require a password from senders. |
| `recipients` | Who receives email notifications when a package arrives. |
| `cloud_connections` | Automatically route uploaded packages to S3, GCS, Wasabi, etc. |
| `custom_webhooks` | Call your endpoint when packages are created or finalized. |
| `file_type_restriction_enabled` / `file_types` | Restrict accepted file extensions. |
| `max_file_size` / `max_package_size` | Set limits on what senders can upload. |
| `package_name_format` | Enforce a naming convention using a regex pattern. |
| `terms_of_service_enabled` | Require senders to accept terms before uploading. |
## Step 2: Connect storage
[Section titled “Step 2: Connect storage”](#step-2-connect-storage)
If you want uploaded files to automatically appear in your cloud storage, you have the option to connect storage (integration) to the portal. MASV supports popular storage providers, such as Amazon S3, Google Cloud Storage, Azure Blob Storage, Backblaze B2, Wasabi, and others. Configure the connection in the MASV Web App or by using the API and reference its ID when creating or updating your portal:
```json
{
"cloud_connections": [
{
"id": "YOUR_CLOUD_CONNECTION_ID",
"target_action": "transfer"
}
]
}
```
Every package that arrives through this portal is automatically delivered to the connected storage location — no manual intervention required.
## Step 3: Register a webhook
[Section titled “Step 3: Register a webhook”](#step-3-register-a-webhook)
You can use a webhook to notify your application of incoming uploads. When you register a webhook on the portal, MASV sends a POST request to your endpoint when a package is created and again when it is finalized.
To attach an existing webhook to the portal, include it in the `custom_webhooks` array when creating or updating the portal. See [Configure Portal webhooks](/tutorials/configure-webhooks/) for steps on creating a webhook and obtaining its ID.
```json
{
"custom_webhooks": [
{
"id": "YOUR_WEBHOOK_ID"
}
]
}
```
When a package is finalized, your server receives:
```json
{
"event_type": "package.finalized",
"object": {
"id": "PACKAGE_ID",
"portal_id": "PORTAL_ID",
"name": "Client Deliverables Upload",
"sender": "client@studio.com",
"size": 2147483648,
"total_files": 47,
"state": "finalized"
}
}
```
## Step 4: Share the portal with senders
[Section titled “Step 4: Share the portal with senders”](#step-4-share-the-portal-with-senders)
**Direct link:** Share `https://yoursubdomain.portal.massive.io` with your senders. They can drag and drop files, add a name and description, and submit the package — no MASV account required.
**Embedded portal:** From the MASV Web App, copy the portal’s embed code and include it in your web page’s HTML. The portal renders inline on your site.
**Custom UI (Pattern 2 approach):** Build your own upload form using the Web Uploader SDK. Resolve your portal’s ID using its subdomain, create packages against the portal ID, and handle uploads as described in [Embed the MASV uploader](/tutorials/embed-uploader/).
## Step 5: Process incoming packages
[Section titled “Step 5: Process incoming packages”](#step-5-process-incoming-packages)
After your webhook fires, you have everything you need to act on the incoming transfer:
```text
package.finalized received
│
├── Look up the package by ID using the MASV API.
├── List the files: GET /packages/{package_id}/files (with X-Package-Token)
├── Download individual files: GET /packages/{package_id}/files/{file_id}/download
└── Trigger your downstream workflow.
```
If you attached a storage connection in Step 2, the files are already in your storage by the time the webhook fires. You can skip the download step and work directly with the files in cloud storage.
## Step 6: Manage portals
[Section titled “Step 6: Manage portals”](#step-6-manage-portals)
You can create and manage portals dynamically — useful for platforms that provision a dedicated intake portal per project, client, or campaign:
```bash
# List all portals in your team
curl -H "X-API-KEY: $API_KEY" \
https://api.massive.app/v1.1/teams/$TEAM_ID/portals
# Update a portal's settings
curl -X PUT "https://api.massive.app/v1/portals/$PORTAL_ID" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ ... }'
# Disable a portal when it is no longer needed
curl -X PUT "https://api.massive.app/v1/portals/$PORTAL_ID" \
-H "X-API-KEY: $API_KEY" \
-H "Content-Type: application/json" \
-d '{ "active": false }'
# Delete a portal
curl -X DELETE "https://api.massive.app/v1/portals/$PORTAL_ID" \
-H "X-API-KEY: $API_KEY"
```
## Production considerations
[Section titled “Production considerations”](#production-considerations)
* **Per-project or per-client portals:** If your platform provisions a portal per project or client, automate portal creation, expiration, and teardown through the API so portals are never left active after they are no longer needed.
* **Access codes:** For sensitive intake workflows, you can configure an `access_code` and distribute it through your own access management system rather than embedding it in a public link. Alternatively, if the contributor is part of your MASV Team, you can require them to sign in to MASV to authenticate (`"user_authentication_required": true`).
* **Webhook idempotency:** MASV retries webhook delivery up to five times. Your handler should record processed event IDs to avoid triggering your downstream workflow multiple times for the same package.
* **Expiry management:** Portal packages expire according to the portal’s `custom_expiry_days` setting. If your downstream processing might take longer than the expiry window, either extend the expiry or download files to your own storage promptly after receiving the `package.finalized` event.
* **Large volumes:** The portal package listing endpoint (`GET /v1.1/portals/{portal_id}/packages`) supports filtering by sender, date range, and tag, making it suitable for periodic polling if you prefer it over webhooks.
# Upload with Agent in Docker
> Upload a file using MASV Agent running in a Docker container — authenticate, upload, track progress, and finalize.
In this tutorial, you’ll follow the steps your application goes through to send a file with MASV. You’ll use the [MASV Agent](/agent/) running in a Docker container to upload a single file to a MASV Team.
MASV Agent abstracts much of the [MASV API](/api/getting-started/) for you. It is a fast, reliable, and flexible way to add file transfers to your application. Because your application interacts with MASV Agent via a RESTful web API, you can run it locally, on-premises, or in the cloud.
## What you’ll learn
[Section titled “What you’ll learn”](#what-youll-learn)
When you’re done, you’ll know how to:
* Set up MASV Agent in Docker
* Authenticate your application
* Start an upload
* Monitor the upload’s progress
* Finalize the upload
## Before you begin
[Section titled “Before you begin”](#before-you-begin)
You’ll need:
* A MASV account with the Owner or Admin [role](https://help.massive.io/en/what-are-the-permissions-for-owner-admins-and-members-of-your-team) — [sign up here](https://app.massive.io/en/signup)
* A command line shell (this tutorial uses [GNU Bash](https://www.gnu.org/software/bash/) on Linux)
* [`curl`](https://curl.se/) — to interact with the MASV Agent HTTP API
* [`jq`](https://jqlang.org/) — to format JSON output
* [Docker](https://www.docker.com/) — Docker Desktop or the Docker CLI with Docker Compose
* A file to send (keep it under 1 MB for this tutorial)
## Step 1: Set up MASV Agent
[Section titled “Step 1: Set up MASV Agent”](#step-1-set-up-masv-agent)
Create a folder to run the MASV Agent. The Agent stores data in two [Docker volumes](https://docs.docker.com/engine/storage/volumes/):
* `config` — MASV Agent configuration
* `data` — uploaded and downloaded files
Create local folders for these volumes before starting the container. If these folders don’t exist, Docker creates them with `root` as the owner.
```bash
mkdir -p send-tutorial/config send-tutorial/data
```
In your text editor, create the `compose.yaml` file below and save it in the `send-tutorial` folder. Replace `YOUR-PATH` with the full path to your local `send-tutorial` folder.
```yaml
services:
transferagent:
image: masvio/masv-agent:3.2.11
container_name: masv-agent
# Store your API key in a .env file — never commit it to version control.
# See /api/api-keys/ for key management best practices.
environment:
- TZ=UTC
- API_KEY=${API_KEY}
volumes:
- YOUR-PATH/send-tutorial/config/:/config
- YOUR-PATH/send-tutorial/data/:/data
ports:
- "127.0.0.1:8080:8080"
restart: unless-stopped
command: ["-api-key", "$API_KEY"]
```
The `compose.yaml` uses a Docker environment variable, `API_KEY`, to authenticate requests to the MASV Agent. You’ll create the API key in the next step.
For full Docker setup details, see the [Docker setup guide](/agent/install-docker/).
Tip
Your application can also use [Docker secrets](https://docs.docker.com/engine/swarm/secrets/) to pass the API key to the MASV Agent container.
## Step 2: Get an API key
[Section titled “Step 2: Get an API key”](#step-2-get-an-api-key)
To authenticate with the MASV Agent, your application needs an [API key](/api/api-keys/).
To create an API key, you must be the MASV account Owner or Admin for a Team.
Caution
Keep the API key secure. It acts like a password that MASV uses to authenticate your requests. Any application that uses this key has the full privileges of your MASV user account.
Store the API key in a file named `.env` in the `send-tutorial` folder. Docker Compose uses this file to pass environment variables to the container.
To get an API key:
1. Sign in to the [MASV Web App](https://app.massive.io/en/login) as the account Owner or Admin.
2. Follow the steps to [create an API key](https://help.massive.io/en/how-to-create-and-manage-api-keys-in-masv).
3. Create a `.env` file in the `send-tutorial` folder with your key:
```text
API_KEY=
```
Caution
Never commit `.env` files to version control. Add `.env` to your `.gitignore` to prevent accidentally pushing secrets to your repository.
## Step 3: Start MASV Agent
[Section titled “Step 3: Start MASV Agent”](#step-3-start-masv-agent)
Open a new terminal window to start the container (so you can see the Agent log while working in the first terminal):
```bash
docker compose up
```
Tip
If you don’t have permission to run Docker, you may need to [add your user to the `docker` group](https://docs.docker.com/engine/install/linux-postinstall/#manage-docker-as-a-non-root-user).
After Docker loads the image and the Agent starts, you’ll see output like this:
```text
masv-agent | [INFO] 20:47:22: Backend: https://api.massive.app
masv-agent | [INFO] 20:47:22: API Server listening on [::]:8080
```
Confirm that the Agent has started a session:
```bash
curl --head --request GET http://localhost:8080/api/v1/login
```
A successful response:
```http
HTTP/1.1 200 OK
Content-Type: application/json
```
If you receive a `401 Unauthorized` response, your API key was not accepted. Create a new key, update the `.env` file, and restart the container.
## Step 4: Get your Team ID
[Section titled “Step 4: Get your Team ID”](#step-4-get-your-team-id)
You need a Team ID to specify which MASV Team to upload to. The `teams` endpoint returns a JSON array of Team objects:
```bash
curl --silent --request GET http://localhost:8080/api/v1/teams | jq '.[] | { name, id }'
```
Example response:
```json
{
"name": "First Unit Team",
"id": "0EGXTA6M1PNLGEV2DJ53FJKGBD"
}
{
"name": "Second Unit Team",
"id": "E1XNAZMFP8LDEJ1HKTW5EBAVNM"
}
```
Choose a Team and save its `id` in a shell variable:
```bash
TEAM_ID="0EGXTA6M1PNLGEV2DJ53FJKGBD" # replace with your Team ID from the response above
```
## Step 5: Start the upload
[Section titled “Step 5: Start the upload”](#step-5-start-the-upload)
A MASV [Package](/api/packages/) is like a virtual folder inside MASV. From the end user’s point of view, a package contains all the files and folders of a single transfer.
The Docker volume for `/data` contains the files that MASV Agent uploads and downloads. Copy your file into the data folder:
```bash
FILE=cat.jpg
cp /path/to/your/"$FILE" send-tutorial/data
```
Now start the upload. Your application provides the Team ID, file paths (absolute paths starting with the container’s `/data` folder), and a package name and description:
```bash
curl --silent --request POST \
-H "Content-Type: application/json" \
http://localhost:8080/api/v1/uploads -d "{ \
\"team_id\":\"$TEAM_ID\", \
\"paths\":[\"/data/$FILE\"], \
\"package_name\": \"My first package\", \
\"package_description\": \"A cat in a package\" \
}" | jq
```
MASV Agent starts uploading immediately in the background and returns:
```json
{
"package_id": "01ENXTA4MFP1LWEXEDKQK7XKV",
"upload_id": "ccea8dc3-3af3-4e0e-b8f0-8cf23d9968e7"
}
```
* `package_id` — unique ID for the package that MASV Agent created
* `upload_id` — unique ID for tracking and managing the upload
Save the upload ID:
```bash
UPLOAD_ID="ccea8dc3-3af3-4e0e-b8f0-8cf23d9968e7" # replace with the upload_id from the response above
```
For full details on upload types and options, see the [Agent Uploads guide](/agent/uploads/).
## Step 6: Track progress
[Section titled “Step 6: Track progress”](#step-6-track-progress)
Use the upload ID to poll the Agent for progress. Poll no more frequently than every 5 seconds.
```bash
curl --request GET http://localhost:8080/api/v1/uploads/$UPLOAD_ID | jq
```
Example response (shortened):
```json
{
"items": [ "..." ],
"name": "My first package",
"progress": 417216,
"size": 417216,
"state": "idle",
"package_name": "My first package",
"package_id": "01ENXTA4MFP1LWEXEDKQK7XKV",
"upload_id": "ccea8dc3-3af3-4e0e-b8f0-8cf23d9968e7"
}
```
Key properties:
* `items` — array of files in the package with individual progress details
* `progress` and `size` — bytes uploaded so far and total package size
* `state` — current upload status
## Step 7: Finalize the upload
[Section titled “Step 7: Finalize the upload”](#step-7-finalize-the-upload)
After the Agent finishes uploading and the `state` is `idle`, finalize the package. Finalizing lets MASV know the package is ready — MASV can then notify recipients, save to cloud integrations, and process any configured automations.
```bash
curl --silent --request POST http://localhost:8080/api/v1/uploads/$UPLOAD_ID/finalize | jq
```
Response:
```json
{
"code": 200
}
```
## Your package is ready
[Section titled “Your package is ready”](#your-package-is-ready)
Your application has authenticated, created a package, uploaded a file, and finalized the upload. The package is now available for download in the MASV Web App.
## Next steps
[Section titled “Next steps”](#next-steps)
* **[MASV Agent](/agent/)** — Full details about adding MASV transfers and automations to your application.
* **[Agent Docker Setup](/agent/install-docker/)** — Complete Docker configuration reference.
* **[Agent Uploads](/agent/uploads/)** — All upload types with CLI and REST API examples.
* **[Web Uploader](/web-sdks/uploader/getting-started/)** — A browser-based uploader SDK for web applications.
* **[MASV API](/api/getting-started/)** — The RESTful API for interacting directly with the MASV cloud.
# Upload with the Web Uploader
> Upload a file from your browser to MASV in minutes using the Web Uploader SDK with a portal.
In this tutorial, you’ll build an end-to-end flow to upload a file from your web application to MASV using a portal and the [Web Uploader SDK](/web-sdks/uploader/getting-started/).
You will:
* Install the MASV Web Uploader SDK
* Resolve a portal from its subdomain
* Create a package for upload
* Initialize the uploader
* Select a file in the browser
* Upload it to MASV
This is the simplest path to a working browser-based upload integration.
## What you’ll build
[Section titled “What you’ll build”](#what-youll-build)
By the end of this tutorial, you will have:
* A simple web page with a file picker
* A JavaScript flow that creates a MASV package
* An uploader instance bound to that package
* A successful file upload to MASV
## Before you begin
[Section titled “Before you begin”](#before-you-begin)
You will need:
* A MASV account — [sign up](https://app.massive.io/en/signup) or [log in](https://app.massive.io/en/login)
* A [Portal](https://help.massive.io/en/how-to-create-a-portal-in-masv) configured in MASV
* The portal’s **subdomain** (for example: `example1234` from `example1234.portal.massive.io`)
* A JavaScript project where you can install npm packages
## Step 1: Install the Web Uploader SDK
[Section titled “Step 1: Install the Web Uploader SDK”](#step-1-install-the-web-uploader-sdk)
Install the MASV Web Uploader package:
```bash
npm install @masvio/uploader
```
Or with Yarn:
```bash
yarn add @masvio/uploader
```
This is the current MASV browser uploader SDK. Use this package for all new web integrations.
## Step 2: Choose the portal you want to upload to
[Section titled “Step 2: Choose the portal you want to upload to”](#step-2-choose-the-portal-you-want-to-upload-to)
Uploads using the Web Uploader are typically sent to a **portal**.
Each portal has a subdomain, for example:
```text
https://example1234.portal.massive.io
```
You will use the subdomain (`example1234`) to look up the portal ID in the next step.
## Step 3: Get the portal ID from the subdomain
[Section titled “Step 3: Get the portal ID from the subdomain”](#step-3-get-the-portal-id-from-the-subdomain)
The uploader flow requires a **portal ID**, not a subdomain.
Call the MASV API to resolve the subdomain. See the [Portals reference](/api/portals/) for full endpoint details.
```text
GET https://api.massive.app/v1/subdomains/portals/{subdomain}
```
This returns the portal object, including its `id`.
Tip
All uploads are tied to a package, and packages are created against a specific portal. The portal ID is required to create that package.
## Step 4: Create a package for the upload
[Section titled “Step 4: Create a package for the upload”](#step-4-create-a-package-for-the-upload)
Uploads are always performed into a **package**. See the [Packages reference](/api/packages/) for the full endpoint specification.
Create a package using the portal ID:
```text
POST https://api.massive.app/v1/portals/{portalID}/packages
```
The response includes:
* `id` — the package ID
* `access_token` — the package token
You will use both of these values to initialize the uploader.
## Step 5: Initialize the uploader
[Section titled “Step 5: Initialize the uploader”](#step-5-initialize-the-uploader)
Create a new uploader instance using the package credentials:
```javascript
import { Uploader } from "@masvio/uploader";
const uploader = new Uploader(packageId, accessToken, "https://api.massive.app");
```
This binds the uploader to the package from the previous step. After initialization, the uploader is ready to accept files.
See the [Web Uploader API Reference](/web-sdks/uploader/api-reference/) for full constructor details.
## Step 6: Add a file picker to your page
[Section titled “Step 6: Add a file picker to your page”](#step-6-add-a-file-picker-to-your-page)
The Web Uploader does not provide a UI — you supply your own.
The simplest approach is a standard HTML file input:
```html
```
When a user selects a file, your application reads it from the browser and prepares it for upload.
## Step 7: Convert selected files into uploader file objects
[Section titled “Step 7: Convert selected files into uploader file objects”](#step-7-convert-selected-files-into-uploader-file-objects)
The uploader expects files in a specific structure. Each file must include:
* A unique `id`
* The browser `File` object
* A `path` (used to preserve folder structure)
Example structure:
```javascript
const files = Array.from(fileInput.files).map((file, index) => ({
id: `file-${index}`,
file,
path: file.name,
}));
```
## Step 8: Add the files to the uploader
[Section titled “Step 8: Add the files to the uploader”](#step-8-add-the-files-to-the-uploader)
After your files are prepared, pass them to the uploader:
```javascript
uploader.addFiles(...files);
```
As soon as files are added:
* The uploader begins processing
* The upload starts automatically
There is no separate “start” call required.
## Step 9: Show progress and completion
[Section titled “Step 9: Show progress and completion”](#step-9-show-progress-and-completion)
Track upload progress and completion by subscribing to [uploader events](/web-sdks/uploader/api-reference/#events):
```javascript
uploader.on(Uploader.UploaderEvents.Progress, (event) => {
console.log("Upload progress:", event.data);
});
uploader.on(Uploader.UploaderEvents.Finished, (event) => {
console.log("Upload complete:", event.data);
});
uploader.on(Uploader.UploaderEvents.Error, (event) => {
console.error("Upload error:", event.data);
});
```
At minimum, you should:
* Show upload progress
* Confirm when upload is complete
* Handle errors gracefully
Note
Unlike the [MASV API](/api/uploads/) and [MASV Agent](/agent/uploads/) workflows, the Web Uploader does not require a manual start or finalize step. After files are added, the upload proceeds automatically.
## Summary
[Section titled “Summary”](#summary)
The minimum upload flow is:
1. Install the Web Uploader SDK
2. Resolve the portal ID from the subdomain
3. Create a package
4. Initialize the uploader
5. Let the user select a file
6. Convert the file into an uploader file object
7. Add the file to the uploader
After files are added, the upload begins immediately.
## Next steps
[Section titled “Next steps”](#next-steps)
* **[Web Uploader Getting Started](/web-sdks/uploader/getting-started/)** — Full guide with a complete code example.
* **[Web Uploader API Reference](/web-sdks/uploader/api-reference/)** — Constructor, methods, and events documentation.
* **[MASV API — Uploads](/api/uploads/)** — Understand the underlying upload lifecycle.
* **[MASV API — Portals](/api/portals/)** — Learn more about portal configuration and management.
# Web SDKs
> Browser SDKs for embedding high-speed uploads and downloads in your web application.
The MASV Web SDKs let you add large file uploads and downloads directly to your web application. They handle chunking, retries, and progress tracking out of the box.
## Web Uploader
[Section titled “Web Uploader”](#web-uploader)
Embed high-speed, browser-based file uploads.
* **[Getting Started](/web-sdks/uploader/getting-started/)** — Install the SDK, create a Package, and upload your first file.
* **[API Reference](/web-sdks/uploader/api-reference/)** — Constructor, methods, events, and configuration options.
## Web Downloader
[Section titled “Web Downloader”](#web-downloader)
Embed high-speed, browser-based file downloads.
* **[Getting Started](/web-sdks/downloader/getting-started/)** — Install the SDK, initialize the downloader, and download your first file.
* **[API Reference](/web-sdks/downloader/api-reference/)** — Constructor, methods, events, and interfaces.
# Web Downloader
> Embed high-speed, browser-based file downloads in your web application using the MASV Web Downloader SDK.
The MASV Web Downloader SDK lets you add large file downloads directly to your web application. It handles chunking, retries, and progress tracking automatically.
## Guides
[Section titled “Guides”](#guides)
* **[Getting Started](/web-sdks/downloader/getting-started/)** — Install the SDK, initialize the downloader, and download your first file.
## Reference
[Section titled “Reference”](#reference)
* **[API Reference](/web-sdks/downloader/api-reference/)** — Constructor, methods, events, and interfaces.
# Web Downloader API Reference
> Complete API reference for the MASV Web Downloader SDK — constructor, methods, events, and interfaces.
This page documents the full API surface of the MASV Web Downloader SDK (`@masvio/downloader`). For a guided walkthrough, see the [Getting Started guide](/web-sdks/downloader/getting-started/).
## Constructor
[Section titled “Constructor”](#constructor)
```javascript
import { Downloader } from "@masvio/downloader";
const downloader = new Downloader(masvLink, linkPassword, userToken, apiBaseURL, chunkSize);
```
| Parameter | Type | Required | Description |
| -------------- | -------- | ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `masvLink` | `string` | Yes | The download Link of the Package being downloaded. Obtained from the Package download page. After instantiation, the downloader can download contents from this Package only. A new downloader must be instantiated for each download attempt (not including pause and resume). |
| `linkPassword` | `string` | Conditional | The password for the MASV download Link. Required only if a password has been set for the link. |
| `userToken` | `string` | Conditional | A MASV user token used for download Links that require user authentication. Required only if user authentication is enabled for the link. |
| `apiBaseURL` | `string` | No | The base URL of the MASV API. Default: `https://api.massive.app`. |
| `chunkSize` | `number` | No | The size of each download chunk in bytes. Default: `104857600` (100 MiB). |
## Methods
[Section titled “Methods”](#methods)
### initialize
[Section titled “initialize”](#initialize)
Required for bootstrapping the Downloader instance. Must be called before `start()`. Implicitly calls [`loadFiles()`](#loadfiles) to retrieve the file list from the download Link.
```javascript
await downloader.initialize();
```
### start
[Section titled “start”](#start)
Starts the download or resumes a paused download.
```javascript
await downloader.start(directoryHandle, fileList);
```
| Parameter | Type | Required | Description |
| ----------------- | --------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `directoryHandle` | `FileSystemDirectoryHandle` | Yes | The directory where the Package contents will be downloaded to. Obtained via [`showDirectoryPicker()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker). |
| `fileList` | `BaseFile[]` | No | An array of files to download. If omitted, all files from the download Link are downloaded. |
### pause
[Section titled “pause”](#pause)
Pauses the download. In-flight chunk requests are aborted. Call [`start()`](#start) to resume.
```javascript
downloader.pause();
```
### retry
[Section titled “retry”](#retry)
Resumes the download and retries any failed files. Only valid when the downloader is in the `PartialFinished` state.
```javascript
await downloader.retry();
```
### cancel
[Section titled “cancel”](#cancel)
Cancels the download. The downloader cannot perform any further downloads after cancellation.
```javascript
downloader.cancel();
```
### terminate
[Section titled “terminate”](#terminate)
Terminates the downloader and all of its workers. The downloader cannot perform any other actions after termination. Use this to clean up resources when the downloader is no longer needed.
```javascript
downloader.terminate();
```
### loadFiles
[Section titled “loadFiles”](#loadfiles)
Retrieves all files from the download Link the Downloader is instantiated with. Returns an array of [`BaseFile`](#basefile) objects.
```javascript
const files = await downloader.loadFiles();
```
## Interfaces
[Section titled “Interfaces”](#interfaces)
### BaseFile
[Section titled “BaseFile”](#basefile)
Represents a file available for download from the Package.
| Property | Type | Description |
| ---------------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | `string` | Unique identifier for the file. |
| `kind` | `string` | The file type. Possible values: `file`, `directory`, `metadata`, `zip_windows`, `zip_mac`. |
| `last_modified` | `string` | The last modified timestamp of the file. |
| `name` | `string` | The file name. |
| `path` | `string` | The relative file path, used to preserve folder structures. May be `undefined`. |
| `size` | `number` | The size of the file in bytes. May be `undefined`. |
| `virus_detected` | `boolean` | Indicates whether a virus has been detected. Files with this field set to `true` are not downloaded and the downloader emits a `FileErrored` event instead. |
| `completed` | `boolean` | Indicates that the file is ready for download. Only relevant for MASV-generated zip files. |
### EventPayloads
[Section titled “EventPayloads”](#eventpayloads)
Every event callback receives an object with the following structure:
| Property | Type | Description |
| -------- | -------- | -------------------------------------------------------------- |
| `event` | `string` | The event name (one of the values listed in the events table). |
| `data` | `object` | Event-specific data relevant to the event type. |
| `time` | `number` | A Unix timestamp indicating when the event was fired. |
| `target` | `object` | The downloader module that fired the event. |
### PerformanceResults
[Section titled “PerformanceResults”](#performanceresults)
Performance statistics returned in `Progress`, `Finished`, `PartialFinished`, `Error`, and `Retry` event payloads.
| Property | Type | Description |
| ---------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `duration` | `number` | Milliseconds elapsed since the download started. |
| `speed` | `number` | Average download speed calculated from total progress and duration. Measured in bits per second. |
| `instant` | `number` | Most recent download speed measurement. Measured in bits per second. |
| `moving` | `number` | Download speed over a recent period (short-term average). Measured in bits per second. |
| `total` | `number` | Size of the entire download in bytes. |
| `progress` | `number` | Total bytes processed from network requests. May decrease after pausing since unwritten data is discarded and some progress is lost. |
| `chunkProgress` | `number` | Total bytes written to disk. May decrease when a file fails and partial chunk progress is lost. |
| `fileProgress` | `number` | Total bytes from fully downloaded files. |
| `totalFiles` | `number` | Number of files included in the transfer. |
| `finalizedFiles` | `number` | Number of successfully downloaded files. |
## Events
[Section titled “Events”](#events)
Subscribe to events using the `on()` method. Attach callbacks **before** calling `start()`.
### Listening for events
[Section titled “Listening for events”](#listening-for-events)
```javascript
downloader.on(Downloader.DownloaderEvents.Progress, ({ data }) => {
console.log("Progress:", data.performanceStats);
});
```
### States
[Section titled “States”](#states)
The downloader emits state change events as it transitions through the download lifecycle:
| Constant | Description |
| ------------- | --------------------------------------------- |
| `Downloading` | The downloader is actively downloading files. |
| `Paused` | The download has been paused. |
| `Cancelled` | The download has been cancelled. |
| `Terminated` | The downloader has been terminated. |
States are accessed via the `Downloader.States` enum:
```javascript
downloader.on(Downloader.States.Downloading, () => {
console.log("Downloading");
});
```
### Event list
[Section titled “Event list”](#event-list)
| Constant | String Value | Data | Description |
| ------------------------ | ----------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Abort` | `download:abort` | `fileId: string` | The download has been aborted after pausing or cancelling the transfer. |
| `Error` | `download:error` | `performanceStats: PerformanceResults; error: Error` | An error has occurred with the downloader. The download cannot continue. |
| `FileDownloaded` | `download:file-downloaded` | `fileId: string` | A file has been successfully downloaded. |
| `FileErrored` | `download:file-errored` | `fileId: string; error: Error` | An error has occurred downloading a specific file. Errored files are automatically skipped, allowing the download to continue. |
| `FileQueued` | `download:file-queued` | `fileId: string` | A file has been queued for download. |
| `Finished` | `download:finish` | `performanceStats: PerformanceResults` | All files have been successfully downloaded. |
| `ParentDirectoryCreated` | `download:parent-directory-created` | `parentDirectoryName: string` | A parent directory named after the Package has been created. This happens before any files are downloaded. |
| `PartialFinished` | `download:partial-finish` | `performanceStats: PerformanceResults; failedFiles: BaseFile[]` | One or more files failed during download. The affected files were skipped and the rest of the download completed successfully. Errored files can be retried with [`retry()`](#retry). |
| `Progress` | `download:progress` | `performanceStats: PerformanceResults` | Download progress report. |
| `Retry` | `download:retry` | `performanceStats: PerformanceResults; error: Error` | The downloader is retrying a request, typically due to a network issue. |
Events are accessed via the `Downloader.DownloaderEvents` enum:
```javascript
Downloader.DownloaderEvents.Progress // "download:progress"
Downloader.DownloaderEvents.Finished // "download:finish"
Downloader.DownloaderEvents.Error // "download:error"
// ... etc.
```
# Web Downloader getting started
> Install the MASV Web Downloader SDK, initialize the downloader, select a directory, and download files in your web application.
The MASV Web Downloader SDK lets you embed high-speed file downloads directly in your web application. It handles chunking, retries, and progress tracking so you can focus on your integration workflow rather than the complexities of the [MASV API](/api/getting-started/).
This guide walks you through installation, downloader initialization, directory handle selection, and download execution.
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
Before you begin, make sure you have:
* A MASV account — [sign up](https://app.massive.io/en/signup) or [log in](https://app.massive.io/en/login)
* A [MASV download Link](/api/links/) for the Package you want to download
* A web application with a JavaScript bundler (Webpack, Vite, etc.)
* A browser that supports the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_API) (Chromium-based browsers)
## Installation
[Section titled “Installation”](#installation)
Install the MASV Web Downloader package:
```shell
npm install @masvio/downloader
```
Or with Yarn:
```shell
yarn add @masvio/downloader
```
## Initialize the Downloader
[Section titled “Initialize the Downloader”](#initialize-the-downloader)
Import the `Downloader` class and create an instance with a [MASV download link](/api/links/) and the link password (if required by the link owner):
```javascript
import { Downloader } from "@masvio/downloader";
const downloader = new Downloader(masvLink, linkPassword);
await downloader.initialize();
```
Each `Downloader` instance is tied to a single download Link. Create a new instance for each download attempt (pause and resume use the same instance).
The `initialize()` method bootstraps the downloader and loads the file list from the link. See the [API Reference](/web-sdks/downloader/api-reference/) for full constructor details.
## Listen for events
[Section titled “Listen for events”](#listen-for-events)
Track download progress and completion by subscribing to events. Attach callbacks **before** calling `start()`:
```javascript
downloader.on(Downloader.States.Downloading, () => {
console.log("State changed: Downloading");
});
downloader.on(Downloader.States.Paused, () => {
console.log("State changed: Paused");
});
downloader.on(Downloader.DownloaderEvents.Progress, ({ data }) => {
console.log("Download progress:", data.performanceStats);
});
downloader.on(Downloader.DownloaderEvents.Finished, () => {
console.log("Download complete!");
});
downloader.on(Downloader.DownloaderEvents.Error, ({ data }) => {
console.error("Download failed:", data.errorMsg);
});
```
See the [API Reference — Events](/web-sdks/downloader/api-reference/#events) for the full list of events and their payloads.
## Select a download directory
[Section titled “Select a download directory”](#select-a-download-directory)
The Web Downloader writes files to a local directory using the browser’s [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/Window/showDirectoryPicker). Call `showDirectoryPicker()` to let the user choose a destination folder:
```html
```
```javascript
let directoryHandle;
const downloadButton = document.getElementById("download");
downloadButton.addEventListener("click", async () => {
directoryHandle = await showDirectoryPicker({
id: "masv-web-downloader",
mode: "readwrite",
startIn: "downloads",
});
});
```
Note
Browsers do not allow users to select system folders (Downloads, Documents, Desktop) directly due to security restrictions, but nested folders within them are allowed. It is recommended that users create a dedicated download folder.
## Start the download
[Section titled “Start the download”](#start-the-download)
Pass the directory handle to the `start()` method to begin downloading all files from the Link:
```javascript
await downloader.start(directoryHandle);
```
To download only specific files, pass a file list as the second argument:
```javascript
const files = await downloader.loadFiles();
const selectedFiles = files.filter((f) => f.name.endsWith(".mov"));
await downloader.start(directoryHandle, selectedFiles);
```
## Complete example
[Section titled “Complete example”](#complete-example)
Here’s a minimal end-to-end integration:
```javascript
import { Downloader } from "@masvio/downloader";
// 1. Initialize Downloader
const downloader = new Downloader(masvLink, linkPassword);
await downloader.initialize();
// 2. Listen for events
downloader.on(Downloader.DownloaderEvents.Progress, ({ data }) => {
console.log("Progress:", data.performanceStats);
});
downloader.on(Downloader.DownloaderEvents.Finished, () => {
console.log("Download finished");
});
downloader.on(Downloader.DownloaderEvents.Error, ({ data }) => {
console.error("Error:", data.errorMsg);
});
// 3. Select directory and start download
const downloadButton = document.getElementById("download");
downloadButton.addEventListener("click", async () => {
const directoryHandle = await showDirectoryPicker({
id: "masv-web-downloader",
mode: "readwrite",
startIn: "downloads",
});
await downloader.start(directoryHandle);
});
```
## Next steps
[Section titled “Next steps”](#next-steps)
* **[API Reference](/web-sdks/downloader/api-reference/)** — Full documentation of the constructor, methods, events, and interfaces.
* **[MASV API — Downloads](/api/downloads/)** — Understand the underlying download lifecycle.
* **[MASV API — Links](/api/links/)** — Learn more about download links and link management.
# Web Uploader
> Embed high-speed, browser-based file uploads in your web application using the MASV Web Uploader SDK.
The MASV Web Uploader SDK lets you add large file uploads directly to your web application. It handles chunking, retries, and progress tracking out of the box.
## Guides
[Section titled “Guides”](#guides)
* **[Getting Started](/web-sdks/uploader/getting-started/)** — Install the SDK, create a Package, and upload your first file.
## Reference
[Section titled “Reference”](#reference)
* **[API Reference](/web-sdks/uploader/api-reference/)** — Constructor, methods, events, and configuration options.
# Web Uploader API Reference
> Complete API reference for the MASV Web Uploader SDK — constructor, methods, events, and data types.
This page documents the full API surface of the MASV Web Uploader SDK (`@masvio/uploader`). For a guided walkthrough, see the [Getting Started guide](/web-sdks/uploader/getting-started/).
## Constructor
[Section titled “Constructor”](#constructor)
```javascript
import { Uploader } from "@masvio/uploader";
const uploader = new Uploader(packageID, packageToken, apiURL);
```
| Parameter | Type | Description |
| -------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
| `packageID` | `string` | The ID of the Package to upload files to. Obtained when creating a Portal Package or a Team Package via the [Packages API](/api/packages/). |
| `packageToken` | `string` | The authorized JWT token for the Package, returned at Package creation time. |
| `apiURL` | `string` | The base URL of the MASV API. Use `https://api.massive.app` for production. |
## Methods
[Section titled “Methods”](#methods)
### addFiles
[Section titled “addFiles”](#addfiles)
Adds one or more files to the upload queue. Uploading begins automatically after the first file is processed.
```javascript
uploader.addFiles(file1, file2, ...fileN);
```
You can also spread an array:
```javascript
const files = [
{ id: "file-0", file: fileObj, path: "" },
{ id: "file-1", file: fileObj2, path: "subfolder/" },
];
uploader.addFiles(...files);
```
Each argument must be a [MasvFile](#masvfile) object.
### start
[Section titled “start”](#start)
Resumes the upload after it has been paused. Only required if the uploader was previously paused with [`pause()`](#pause).
```javascript
uploader.start();
```
### pause
[Section titled “pause”](#pause)
Pauses the upload. In-flight chunk requests are aborted. Call [`start()`](#start) to resume.
```javascript
uploader.pause();
```
### cancel
[Section titled “cancel”](#cancel)
Cancels the upload. Stops all in-flight transfers and prevents further uploads.
```javascript
uploader.cancel();
```
### finalize
[Section titled “finalize”](#finalize)
Finalizes the upload. Signals that all files have been added and the Package is ready for delivery.
```javascript
uploader.finalize();
```
### terminate
[Section titled “terminate”](#terminate)
Terminates the uploader instance and all of its workers. Use this to clean up resources when the uploader is no longer needed.
```javascript
uploader.terminate();
```
### getPerformanceStats
[Section titled “getPerformanceStats”](#getperformancestats)
Returns statistics about the uploader’s performance, including throughput and transfer metrics.
```javascript
const stats = uploader.getPerformanceStats();
```
## Data types
[Section titled “Data types”](#data-types)
### MasvFile
[Section titled “MasvFile”](#masvfile)
The file object format expected by [`addFiles()`](#addfiles).
| Property | Type | Description |
| -------- | -------- | ------------------------------------------------------------------------------------------------ |
| `id` | `string` | A unique identifier for this file upload. |
| `file` | `File` | The browser [`File`](https://developer.mozilla.org/en-US/docs/Web/API/File) object. |
| `path` | `string` | The file path used to preserve folder structure on the receiving end. Use `""` for flat uploads. |
## Events
[Section titled “Events”](#events)
Subscribe to events using the `on()` method. You can listen for a specific event or delegate a handler for all events.
### Listening for a specific event
[Section titled “Listening for a specific event”](#listening-for-a-specific-event)
```javascript
uploader.on(Uploader.UploaderEvents.Progress, (event) => {
console.log(event);
});
```
### Delegating all events
[Section titled “Delegating all events”](#delegating-all-events)
```javascript
const handlers = {
[Uploader.UploaderEvents.Progress]: (e) => console.log("Progress:", e),
[Uploader.UploaderEvents.Finished]: (e) => console.log("Done:", e),
};
uploader.on("emit", (event) => {
if (event.name in handlers) {
handlers[event.name](event);
}
});
```
### Event payload
[Section titled “Event payload”](#event-payload)
Every event callback receives an object with the following structure:
| Property | Type | Description |
| -------- | -------- | ------------------------------------------------------------- |
| `event` | `string` | The event name (one of the values listed in the table below). |
| `time` | `number` | A Unix timestamp indicating when the event was fired. |
| `target` | `object` | The uploader module that fired the event. |
| `data` | `object` | Event-specific data relevant to the event type. |
### Event list
[Section titled “Event list”](#event-list)
| Constant | String Value | Description |
| ----------------- | ------------------------ | ---------------------------------------------------------------------------------------- |
| `Created` | `uploader:create` | The uploader instance is initialized. |
| `Start` | `upload:start` | The uploader begins uploading data — either at the start of the upload or after a pause. |
| `FileQueued` | `upload:file_queued` | A file is queued for upload. |
| `Progress` | `upload:progress` | Data is successfully sent. Use this to update progress indicators. |
| `Chunk` | `upload:chunk` | A chunk of a file has finished uploading. |
| `File` | `upload:file` | All chunks for a file have finished uploading. |
| `Finalize` | `upload:finalize` | The uploader has finalized a file upload. |
| `Error` | `upload:error` | An error occurred during an upload request. |
| `Finished` | `upload:finish` | The uploader has finished uploading all files. |
| `Abort` | `upload:abort` | An upload is aborted, typically when the uploader is paused. |
| `Retry` | `upload:retry` | The uploader is retrying a request, typically due to a network issue. |
| `File Unreadable` | `upload:file_unreadable` | The browser is unable to read one of the files in the Package. |
| `Stalled` | `upload:stalled` | No upload progress has been reported for at least 1 minute. |
Events are accessed via the `Uploader.UploaderEvents` enum:
```javascript
Uploader.UploaderEvents.Progress // "upload:progress"
Uploader.UploaderEvents.Finished // "upload:finish"
Uploader.UploaderEvents.Error // "upload:error"
// ... etc.
```
# Web Uploader getting started
> Install the MASV Web Uploader SDK, resolve a Portal, create a Package, and upload files from your web application.
The MASV Web Uploader SDK lets you embed high-speed file uploads directly in your web application. It handles chunking, retries, and progress tracking so you can focus on your integration workflow rather than the complexities of the [MASV API](/api/getting-started/).
This guide walks you through installation, Portal resolution, Package creation, uploader initialization, file selection, and upload execution.
## Prerequisites
[Section titled “Prerequisites”](#prerequisites)
Before you begin, make sure you have:
* A MASV account — [sign up](https://app.massive.io/en/signup) or [log in](https://app.massive.io/en/login)
* A Portal with a known subdomain (for example, `example1234` from `example1234.portal.massive.io`). [Create a new Portal](https://help.massive.io/en/how-to-create-a-portal-in-masv) or use an existing one.
* A web application with a JavaScript bundler (Webpack, Vite, etc.)
## Installation
[Section titled “Installation”](#installation)
Install the MASV Web Uploader package:
```shell
npm install @masvio/uploader
```
Or with Yarn:
```shell
yarn add @masvio/uploader
```
## Resolve the Portal ID
[Section titled “Resolve the Portal ID”](#resolve-the-portal-id)
The Web Uploader sends files to a MASV Package, which belongs to a Portal. Use the Portal’s subdomain to look up its ID via the [Portals API](/api/portals/):
```javascript
async function fetchPortalID(subdomain) {
const response = await fetch(
`https://api.massive.app/v1/subdomains/portals/${subdomain}`
);
const { id } = await response.json();
return id;
}
```
Tip
This example uploads to a Portal, but you can also upload to a MASV Team. See the [Packages API](/api/packages/) for Team-based Package creation.
## Create a Package
[Section titled “Create a Package”](#create-a-package)
Create a [Package](/api/packages/) on the Portal to hold the uploaded files:
```javascript
async function createPackage(portalID) {
const response = await fetch(
`https://api.massive.app/v1/portals/${portalID}/packages`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
name: "My Upload",
sender: "you@example.com",
description: "Files uploaded via Web Uploader",
}),
}
);
const { id, access_token } = await response.json();
return { id, access_token };
}
```
The response provides a Package `id` and an `access_token` — both are required to initialize the uploader.
## Initialize the Uploader
[Section titled “Initialize the Uploader”](#initialize-the-uploader)
Import the `Uploader` class and create an instance with the Package credentials:
```javascript
import { Uploader } from "@masvio/uploader";
const portalID = await fetchPortalID("your-subdomain");
const { id, access_token } = await createPackage(portalID);
const uploader = new Uploader(id, access_token, "https://api.massive.app");
```
See the [API Reference](/web-sdks/uploader/api-reference/) for full constructor details.
## Select files
[Section titled “Select files”](#select-files)
Use a standard HTML file input to let users pick files:
```html
```
Collect the selected files into the format the uploader expects:
```javascript
const fileInput = document.getElementById("fileInput");
let files = [];
fileInput.addEventListener("input", () => {
files = Array.from(fileInput.files).map((file, index) => ({
id: `file-${index}`,
file,
path: "",
}));
});
```
Each file object requires an `id` (a unique string), the `file` (a browser `File` object), and a `path` (used to preserve folder structure on the receiving end).
## Upload the files
[Section titled “Upload the files”](#upload-the-files)
Pass the selected files to the uploader. Uploading begins immediately after the first file is processed:
```javascript
uploader.addFiles(...files);
```
Note
Unlike the [MASV API](/api/uploads/) and [MASV Agent](/agent/uploads/), you do not need to explicitly start or finalize the upload with the Web Uploader. The SDK handles this automatically.
## Listen for events
[Section titled “Listen for events”](#listen-for-events)
Track upload progress and completion by subscribing to events:
```javascript
uploader.on(Uploader.UploaderEvents.Progress, (event) => {
console.log("Upload progress:", event.data);
});
uploader.on(Uploader.UploaderEvents.Finished, (event) => {
console.log("Upload complete:", event.data);
});
uploader.on(Uploader.UploaderEvents.Error, (event) => {
console.error("Upload error:", event.data);
});
```
See the [API Reference — Events](/web-sdks/uploader/api-reference/#events) for the full list of events and their payloads.
## Complete example
[Section titled “Complete example”](#complete-example)
Here’s a minimal end-to-end integration:
```javascript
import { Uploader } from "@masvio/uploader";
// 1. Resolve Portal
const portalID = await fetchPortalID("your-subdomain");
// 2. Create Package
const { id, access_token } = await createPackage(portalID);
// 3. Initialize Uploader
const uploader = new Uploader(id, access_token, "https://api.massive.app");
// 4. Listen for events
uploader.on(Uploader.UploaderEvents.Progress, (event) => {
console.log("Progress:", event.data);
});
uploader.on(Uploader.UploaderEvents.Finished, (event) => {
console.log("Upload finished");
});
uploader.on(Uploader.UploaderEvents.Error, (event) => {
console.error("Error:", event.data);
});
// 5. Add files (from a file input)
const fileInput = document.getElementById("fileInput");
fileInput.addEventListener("input", () => {
const files = Array.from(fileInput.files).map((file, i) => ({
id: `file-${i}`,
file,
path: "",
}));
uploader.addFiles(...files);
});
```
## Next steps
[Section titled “Next steps”](#next-steps)
* **[API Reference](/web-sdks/uploader/api-reference/)** — Full documentation of the constructor, methods, and events.
* **[Upload with Web Uploader tutorial](/tutorials/upload-web-uploader/)** — A step-by-step Quick Start tutorial.
* **[MASV API — Uploads](/api/uploads/)** — Understand the underlying upload lifecycle.
* **[MASV API — Portals](/api/portals/)** — Learn more about Portal configuration and management.