| Step | Call | Purpose |
|---|---|---|
| 1 | POST /v1/files/upload/start | Register the file and receive a file_handle |
| 2 | POST /v1/files/upload/part/{part_number} | Get a pre-signed URL for one part (repeat per part) |
| 3 | PUT {presigned_url} | Upload the part's bytes directly to storage and keep the returned ETag |
| 4 | POST /v1/files/upload/finish | Assemble the parts into the final file (optionally activate it) |
| 5 | GET /v1/files/upload/status | (Optional) Poll processing status and get a preview URL |
| 6 | POST /v1/files/upload/activate | Activate the file, if it wasn't activated in step 4 |
file_type; otherwise start returns 403 Forbidden.part_number = 1).ETag response header from each PUT to storage — you need it to finish the upload.| Status | Meaning |
|---|---|
upload_in_progress | Upload started; parts are being uploaded |
completing | finish was called; parts are being assembled |
completed | File is stored but not yet active |
active | File is active and ready to use |
POST /v1/files/upload/start| Field | Type | Required | Description |
|---|---|---|---|
file_type | string | Yes | Type of file in MPC. Determines permissions and validation rules. |
filename | string | Yes | Original file name, e.g. receipt.jpg. |
mimetype | string | Yes | MIME type, e.g. image/jpeg. |
size | integer | Yes | Total file size in bytes. |
file_identifier | string | No | ID of the object the file belongs to. Can also be set in finish or activate. |
file_subidentifier | string | No | Additional identifier of the object the file belongs to. |
{
"file_type": "receipt",
"filename": "20240927_164634.jpg",
"mimetype": "image/jpeg",
"size": 312077,
"file_identifier": "12345"
}200 OK| Field | Type | Description |
|---|---|---|
file_handle | string | Unique, 32-character identifier of the file. Use it in every following call. |
{
"file_handle": "q8ZkT3mVn0aX7pLr2cYbH5wEjF9sGd1u"
}| Status | When |
|---|---|
400 / 422 | Missing or invalid field (e.g. unsupported mimetype, size above the limit for this file_type) |
401 | Missing or invalid credentials |
403 | Not permitted to upload this file_type |
POST /v1/files/upload/part/{part_number}| Parameter | Type | Description |
|---|---|---|
part_number | integer | Part number, from 1 to 10000. |
| Field | Type | Required | Description |
|---|---|---|---|
file_handle | string | Yes | Value returned by start. |
size | integer | Yes | Size of this part in bytes. |
md5 | string | Yes | Base64-encoded MD5 digest of this part's bytes. Storage rejects the part if it doesn't match. |
{
"file_handle": "q8ZkT3mVn0aX7pLr2cYbH5wEjF9sGd1u",
"size": 312077,
"md5": "dqt0LFjuwom3N7+rgvhTIg=="
}200 OK| Field | Type | Description |
|---|---|---|
url | string | Pre-signed URL to PUT the part to. The URL expires after a short time; request a new one if it does. |
headers | object | Headers you must send, unchanged, with the PUT. |
part | integer | Part number this URL is for. |
{
"url": "https://<bucket>.s3.eu-central-1.amazonaws.com/<key>?partNumber=1&uploadId=...&X-Amz-Signature=...",
"headers": {
"Content-MD5": "dqt0LFjuwom3N7+rgvhTIg==",
"Content-Type": "image/jpeg"
},
"part": 1
}| Status | When |
|---|---|
404 | Unknown file_handle |
422 | Invalid part_number, size or md5, or the upload is no longer in progress |
PUT {url}url from step 2, with all headers from step 2. Do not add an Authorization header — authorization is in the URL.200 OK with an ETag response header, for example:ETag: "76ab742c58eec289b737bfab82f85322"ETag (including the quotes) together with the part number. If the upload fails, retry the same part — get a new URL if the old one has expired.POST /v1/files/upload/finishcompleting and then to completed (or active, if activate is true).| Field | Type | Required | Description |
|---|---|---|---|
file_handle | string | Yes | Value returned by start. |
parts | array | Yes | Every uploaded part, sorted by part_number. |
parts[].part_number | integer | Yes | Part number. |
parts[].etag | string | Yes | ETag returned by storage for this part. |
activate | boolean | No | Activate the file as soon as it's completed. Default false. Requires file_identifier to be set. |
file_identifier | string | No | Sets or overrides the value given in start. |
file_subidentifier | string | No | Sets or overrides the value given in start. |
{
"file_handle": "q8ZkT3mVn0aX7pLr2cYbH5wEjF9sGd1u",
"activate": true,
"file_identifier": "12345",
"parts": [
{ "part_number": 1, "etag": "\"76ab742c58eec289b737bfab82f85322\"" }
]
}202 Accepted{
"file_handle": "q8ZkT3mVn0aX7pLr2cYbH5wEjF9sGd1u",
"status": "completing"
}| Status | When |
|---|---|
404 | Unknown file_handle |
422 | Missing or invalid parts, or the upload was already finished |
GET /v1/files/upload/status?file_handle={file_handle}finish. Poll with a back-off (e.g. every 1–2 seconds).| Parameter | Type | Required | Description |
|---|---|---|---|
file_handle | string | Yes | Value returned by start. |
200 OK| Field | Type | Description |
|---|---|---|
status | string | One of upload_in_progress, completing, completed, active. See File lifecycle. |
url | string | null | URL to fetch the file, e.g. for a preview. Available once the file is completed. |
{
"status": "completed",
"url": "https://<files-cdn>/q8ZkT3mVn0aX7pLr2cYbH5wEjF9sGd1u"
}POST /v1/files/upload/activate"activate": true in finish.| Field | Type | Required | Description |
|---|---|---|---|
file_handle | string | Yes | Value returned by start. |
file_identifier | string | No* | Sets or overrides the stored value. *Required if it wasn't set in start or finish. |
file_subidentifier | string | No | Sets or overrides the stored value. |
{
"file_handle": "q8ZkT3mVn0aX7pLr2cYbH5wEjF9sGd1u",
"file_identifier": "12345"
}200 OK{
"file_handle": "q8ZkT3mVn0aX7pLr2cYbH5wEjF9sGd1u",
"status": "active"
}| Status | When |
|---|---|
404 | Unknown file_handle |
422 | file_identifier is not set on the file |
422 | Upload is not completed yet — check status and retry |
| Problem | Cause and fix |
|---|---|
Storage returns 403 SignatureDoesNotMatch | Headers sent with the PUT differ from the ones returned in step 2. Send them exactly as returned. |
Storage returns 400 BadDigest | md5 doesn't match the bytes sent. Compute it over exactly the bytes of that part. |
Storage returns 403 Request has expired | The pre-signed URL expired. Request a new one for the same part. |
ETag is null in the browser | The response header isn't readable by your script. Contact MPC support. |
finish returns 422 | A part is missing, too small (under 5 MiB and not the last), or an ETag is wrong. |
activate returns 422 | Set file_identifier, or wait for status completed. |