CreateMultipartUpload
CreateMultipartUpload is the first step of a multipart upload: it opens an upload for an object and hands back an upload ID. The object's parts then follow with UploadPart or UploadPartCopy, and CompleteMultipartUpload assembles them into the object - or AbortMultipartUpload discards everything. The object's metadata, encryption and Object Lock settings belong to this request, not to the parts.
A single PutObject stores up to 5 GiB, so a larger object travels this way. For any large object, a multipart upload also means parts can go up in parallel, and a part that fails is simply sent again on its own.
- Parts are 5,000,000 bytes to 5 GiB each (the last part may be smaller), numbered 1-10,000.
x-amz-taggingand the Object Lock event-hold headers answer501 NotImplemented, so a client knows straight away.- Encryption is
AES256with STRG-managed keys, or SSE-C with your own key. SSE-KMS answers501 NotImplemented. - An object is always exactly as public as its bucket:
x-amz-acltakesprivateorpublic-read, matching the bucket's Bucket Protection setting, andx-amz-grant-*headers answer501 NotImplemented. x-amz-expected-bucket-owneris welcome and has nothing to check: a key only ever reaches buckets of its own account.- A bucket in another region answers
404 NoSuchBucketrather than a redirect.
The parts of an upload that is never completed or aborted stay stored, and are billed as stored data. Each upload ends well with either CompleteMultipartUpload or AbortMultipartUpload, and a lifecycle rule that aborts incomplete multipart uploads catches the ones a crashed script leaves behind - Cleaning up abandoned multipart uploads shows one.
When you'd use it
- A nightly backup of a database dump or a disk image, often well over 5 GiB. The backup tool starts a multipart upload, sends the file in parts and completes it, so a dropped connection costs one part rather than the whole file.
- A video platform taking uploads of large recordings. Its server opens the upload here, and the parts can go up in parallel to finish sooner.
- A data pipeline writing a large export as it is produced. The object's content type and metadata are set once, at the start, and parts follow as the data becomes ready.
- Anything using the AWS CLI's
aws s3 cpor an SDK's transfer helper. Above a size threshold they call CreateMultipartUpload for you, so it is often at work without being called by name.
Request
POST /photos/2024/cat.jpg?uploads HTTP/1.1
Host: my-bucket.s3.eu-central-1.strg.com
Authorization: AWS4-HMAC-SHA256 Credential=...
x-amz-date: 20260926T120000Z
x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
Content-Type: image/jpeg
x-amz-meta-camera: pinhole
Path-style, the same request is POST /my-bucket/photos/2024/cat.jpg?uploads on
s3.eu-central-1.strg.com - Addressing explains both forms.
Permissions
Starting an upload takes Write objects (objects:write). For a key limited to certain
buckets or to a key prefix, the bucket and the object key need to be within them.
Locking the object as it is uploaded takes a little more, the same permission setting the lock afterwards would:
| Header | Also takes |
|---|---|
x-amz-object-lock-mode, x-amz-object-lock-retain-until-date | Set retention (object-retention:write) |
x-amz-object-lock-legal-hold | Set legal holds (object-legal-hold:write) |
Without it, STRG answers 403 AccessDenied. Access keys has every
permission.
URI parameters
| Name | Type | Required | Description |
|---|---|---|---|
Bucket | String | Yes | The bucket to upload into. |
Key | String | Yes | The key of the object to create. A key with a . or .. path segment (a/../b) cannot be put in a URL, and STRG answers 400 InvalidURI. |
uploads | - | Yes | Present, with no value. It marks the request as CreateMultipartUpload. |
Any other query parameter is answered with 501 NotImplemented.
Request headers
| Name | Type | Required | Description |
|---|---|---|---|
Cache-Control | String | No | Stored with the object and returned when it is read. |
Content-Disposition | String | No | Stored with the object and returned when it is read. |
Content-Encoding | String | No | Stored with the object and returned when it is read. |
Content-Language | String | No | Stored with the object and returned when it is read. |
Content-Type | String | No | The object's media type, returned when it is read. |
Expires | Timestamp | No | Stored with the object and returned when it is read. |
x-amz-meta-* | String | No | User-defined metadata, stored with the object. |
x-amz-acl | String | No | private or public-read, and only the value that matches the bucket: private for a bucket with Bucket Protection on, public-read for one with it off. Any other canned ACL is 501 NotImplemented. An object is always exactly as public as its bucket. |
x-amz-grant-full-control | String | No | 501 NotImplemented: access comes from the access key and the bucket's Bucket Protection. |
x-amz-grant-read | String | No | 501 NotImplemented. |
x-amz-grant-read-acp | String | No | 501 NotImplemented. |
x-amz-grant-write-acp | String | No | 501 NotImplemented. |
x-amz-server-side-encryption | String | No | AES256 encrypts the object with STRG-managed keys. aws:kms and aws:kms:dsse are 501 NotImplemented. |
x-amz-server-side-encryption-customer-algorithm | String | No | SSE-C: AES256. The same three SSE-C headers go on every UploadPart of the upload. |
x-amz-server-side-encryption-customer-key | String | No | SSE-C: your base64-encoded 256-bit key. STRG does not store it, so losing it means losing the object. |
x-amz-server-side-encryption-customer-key-MD5 | String | No | SSE-C: the base64-encoded MD5 digest of the key. |
x-amz-server-side-encryption-aws-kms-key-id | String | No | 501 NotImplemented. |
x-amz-server-side-encryption-context | String | No | 501 NotImplemented. |
x-amz-server-side-encryption-bucket-key-enabled | Boolean | No | 501 NotImplemented. |
x-amz-storage-class | String | No | STRG has a single storage class, so this header is best left out. |
x-amz-website-redirect-location | String | No | STRG does not host static websites, so this header has nothing to act on and is best left out. |
x-amz-tagging | String | No | 501 NotImplemented. Objects carry no tags. |
x-amz-object-lock-mode | String | No | GOVERNANCE or COMPLIANCE. Works in a bucket with Object Lock enabled, together with x-amz-object-lock-retain-until-date, and takes the Set retention permission. Object Lock explains the modes. |
x-amz-object-lock-retain-until-date | Timestamp | No | When the retention ends, in ISO 8601 (2027-01-01T00:00:00Z). Takes Set retention. |
x-amz-object-lock-legal-hold | String | No | ON or OFF. Works in a bucket with Object Lock enabled and takes the Set legal holds permission. |
x-amz-object-lock-event-hold | String | No | 501 NotImplemented. |
x-amz-object-lock-event-hold-duration-days | Integer | No | 501 NotImplemented. |
x-amz-object-lock-event-hold-duration-years | Integer | No | 501 NotImplemented. |
x-amz-checksum-algorithm | String | No | The checksum algorithm the parts will carry, such as CRC32. Every part then sends a checksum of that algorithm. |
x-amz-checksum-type | String | No | Accepted and passed on with the request. |
x-amz-expected-bucket-owner | String | No | Welcome, with nothing to check: an access key only ever reaches its own account's buckets. |
x-amz-request-payer | String | No | Not applicable: STRG has no Requester Pays buckets. |
Every x-amz-* header in the request is part of the signature - the AWS CLI and SDKs take care
of that. One left unsigned is answered with 403 AccessDenied.
Request body
None.
Response
A successful request answers 200 OK with the upload ID in an XML body.
Response headers
| Name | Description |
|---|---|
x-amz-request-id | The request ID, handy for support requests. |
x-amz-server-side-encryption | The server-side encryption used for the object, when it is encrypted. |
x-amz-server-side-encryption-customer-algorithm | For SSE-C, the algorithm you sent. |
x-amz-server-side-encryption-customer-key-MD5 | For SSE-C, the MD5 digest of the key you sent. |
x-amz-checksum-algorithm | The checksum algorithm of the upload, when you set one. |
x-amz-checksum-type | The checksum type of the upload, when there is one. |
x-amz-server-side-encryption-aws-kms-key-id, x-amz-server-side-encryption-context,
x-amz-server-side-encryption-bucket-key-enabled and x-amz-request-charged are never returned.
x-amz-abort-date and x-amz-abort-rule-id are not something to count on for learning whether a
lifecycle rule will abort the upload; the bucket's rules, read with
GetBucketLifecycleConfiguration, are the reliable answer.
Response body
| Element | Description |
|---|---|
Bucket | The bucket the upload is in. |
Key | The object key the upload is for. |
UploadId | The upload ID. Every later request for this upload carries it unchanged; it is an opaque string. |
<?xml version="1.0" encoding="UTF-8"?>
<InitiateMultipartUploadResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Bucket>my-bucket</Bucket>
<Key>photos/2024/cat.jpg</Key>
<UploadId>EXAMPLE-UPLOAD-ID</UploadId>
</InitiateMultipartUploadResult>
Errors
These are the answers a CreateMultipartUpload can get besides 200 OK, and the situation behind
each:
| Code | HTTP status | When |
|---|---|---|
NoSuchBucket | 404 | The bucket does not exist, or it is in a region other than the endpoint's. |
InvalidURI | 400 | The key contains a . or .. path segment, such as a/../b. Nothing is started. |
AccessDenied | 403 | The access key lacks Write objects, or Set retention / Set legal holds for an Object Lock header, or the bucket or key is outside its buckets or object prefix, or an x-amz-* header is not signed. |
NotImplemented | 501 | The request carries x-amz-tagging, an event-hold header, SSE-KMS, an x-amz-grant-* header, a canned ACL other than private or public-read, or a query parameter this operation does not take. |
InvalidAccessKeyId | 403 | The access key does not exist, has expired or been revoked, or it is not yet usable in a region where you already store data - a key created a moment ago, which works again shortly. |
SignatureDoesNotMatch | 403 | The request signature does not match. |
RequestTimeTooSkewed | 403 | The request time is more than 15 minutes from the server's. |
AuthorizationHeaderMalformed | 400 | The signature's credential scope names a region other than the endpoint's. |
SlowDown | 503 | Too many requests at once. The SDKs retry after the Retry-After interval. |
ServiceUnavailable | 503 | A temporary failure; the request succeeds when retried. |
Differences from Amazon S3
Most of these make STRG simpler to reason about; each says what a client sees.
| Aspect | Amazon S3 | STRG |
|---|---|---|
| Part size | 5 MiB minimum (except the last part), 5 GiB maximum | 5,000,000 bytes minimum (except the last part), 5 GiB maximum |
| Part numbers | 1-10,000 | 1-10,000 |
| Bucket in another region | 301 redirect with x-amz-bucket-region | 404 NoSuchBucket. The bucket's own regional endpoint reaches it |
x-amz-tagging | Tags the object | 501 NotImplemented |
| Object Lock event holds | Supported | 501 NotImplemented |
| SSE-KMS | Supported | 501 NotImplemented. AES256 with STRG-managed keys, or SSE-C |
| ACLs | Any canned ACL, and grants | private or public-read, matching the bucket. Grants are 501 NotImplemented |
| Storage classes | Several | One, so x-amz-storage-class is best left out |
| Website redirects | x-amz-website-redirect-location | STRG does not host static websites |
| Object Lock headers | Need s3:PutObjectRetention / s3:PutObjectLegalHold | Take Set retention / Set legal holds on the access key |
x-amz-expected-bucket-owner | Checked against the bucket owner | Nothing to check: a key reaches only its own account's buckets |
Keys with a . or .. path segment | Accepted | 400 InvalidURI |
Examples
Starting an upload
This starts an upload for photos/2024/cat.jpg with a content type and one metadata entry, and
keeps the upload ID for the parts that follow:
- AWS CLI
- Python (boto3)
- JavaScript (AWS SDK v3)
UPLOAD_ID=$(aws s3api create-multipart-upload \
--bucket my-bucket \
--key photos/2024/cat.jpg \
--content-type image/jpeg \
--metadata camera=pinhole \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1 \
--query UploadId \
--output text)
echo "$UPLOAD_ID"
import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://s3.eu-central-1.strg.com",
region_name="eu-central-1",
)
response = s3.create_multipart_upload(
Bucket="my-bucket",
Key="photos/2024/cat.jpg",
ContentType="image/jpeg",
Metadata={"camera": "pinhole"},
)
print(response["UploadId"])
import { CreateMultipartUploadCommand, S3Client } from "@aws-sdk/client-s3";
const client = new S3Client({
endpoint: "https://s3.eu-central-1.strg.com",
region: "eu-central-1",
});
const response = await client.send(
new CreateMultipartUploadCommand({
Bucket: "my-bucket",
Key: "photos/2024/cat.jpg",
ContentType: "image/jpeg",
Metadata: { camera: "pinhole" },
}),
);
console.log(response.UploadId);
Uploading a large file
The high-level transfer tools call CreateMultipartUpload, UploadPart and CompleteMultipartUpload for you, and abort the upload if it fails. A custom part size works anywhere between 5,000,000 bytes and 5 GiB, as long as the whole file fits in 10,000 parts.
- AWS CLI
- Python (boto3)
- JavaScript (AWS SDK v3)
aws s3 cp ./backup.tar s3://my-bucket/backups/backup.tar \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1
import boto3
from boto3.s3.transfer import TransferConfig
s3 = boto3.client(
"s3",
endpoint_url="https://s3.eu-central-1.strg.com",
region_name="eu-central-1",
)
s3.upload_file(
"./backup.tar",
"my-bucket",
"backups/backup.tar",
Config=TransferConfig(
multipart_threshold=64 * 1024 * 1024,
multipart_chunksize=64 * 1024 * 1024,
),
)
This one uses @aws-sdk/lib-storage beside @aws-sdk/client-s3.
import { createReadStream } from "node:fs";
import { S3Client } from "@aws-sdk/client-s3";
import { Upload } from "@aws-sdk/lib-storage";
const client = new S3Client({
endpoint: "https://s3.eu-central-1.strg.com",
region: "eu-central-1",
});
const upload = new Upload({
client,
params: {
Bucket: "my-bucket",
Key: "backups/backup.tar",
Body: createReadStream("./backup.tar"),
},
partSize: 64 * 1024 * 1024,
});
await upload.done();
Locking the object as it is uploaded
In a bucket with Object Lock enabled, this starts an upload whose object is retained in governance mode until 1 January 2027. The access key takes Set retention beside Write objects.
- AWS CLI
- Python (boto3)
- JavaScript (AWS SDK v3)
aws s3api create-multipart-upload \
--bucket my-bucket \
--key photos/2024/cat.jpg \
--object-lock-mode GOVERNANCE \
--object-lock-retain-until-date 2027-01-01T00:00:00Z \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1
from datetime import datetime, timezone
import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://s3.eu-central-1.strg.com",
region_name="eu-central-1",
)
response = s3.create_multipart_upload(
Bucket="my-bucket",
Key="photos/2024/cat.jpg",
ObjectLockMode="GOVERNANCE",
ObjectLockRetainUntilDate=datetime(2027, 1, 1, tzinfo=timezone.utc),
)
print(response["UploadId"])
import { CreateMultipartUploadCommand, S3Client } from "@aws-sdk/client-s3";
const client = new S3Client({
endpoint: "https://s3.eu-central-1.strg.com",
region: "eu-central-1",
});
const response = await client.send(
new CreateMultipartUploadCommand({
Bucket: "my-bucket",
Key: "photos/2024/cat.jpg",
ObjectLockMode: "GOVERNANCE",
ObjectLockRetainUntilDate: new Date("2027-01-01T00:00:00Z"),
}),
);
console.log(response.UploadId);