Skip to main content

UploadPart

UploadPart sends one piece of an object that is being uploaded in parts. A multipart upload opens with CreateMultipartUpload, which hands back an upload ID; each part then goes up with UploadPart - or is copied from an object already stored, with UploadPartCopy - and CompleteMultipartUpload assembles the parts into the object, given the ETag each part returned.

The part number, 1-10,000, sets the part's position in the finished object. Sending a part again under the same number replaces the earlier one, which is what makes a failed part so easy to retry: only that part travels again.

Good to know on STRG
  • Every part but the last is at least 5,000,000 bytes, rather than 5 MiB, so a part size chosen for Amazon S3 works unchanged. A part can be up to 5 GiB.
  • A body sent with Transfer-Encoding: chunked and no Content-Length is answered 411 MissingContentLength. aws-chunked uploads, which the AWS SDKs send, work as they are.
  • x-amz-expected-bucket-owner is welcome and has nothing to check: a key only ever reaches buckets of its own account.
  • A bucket in another region answers 404 NoSuchBucket rather than a redirect.
Unfinished uploads are billed

Uploaded parts stay stored, and are billed as stored data, until their upload is completed or aborted. 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 large database dump. The backup tool sends the file in parts of, say, 64 MiB, so a dropped connection costs one part rather than the whole file, and the upload picks up where it stopped.
  • A server uploading a long video recording. Several parts can go up at once, over several connections, which finishes a large upload sooner than one long request.
  • A web app taking large uploads straight from the browser. The app's server starts the upload and presigns one UploadPart URL per part; the browser sends each part with a plain PUT, and the server completes the upload with the ETags it collected. The bucket needs CORS rules for the browser to reach it.
  • Anything using the AWS CLI's aws s3 cp or an SDK's transfer helper. Above a size threshold they split the file and call UploadPart for each piece, so it is often at work without being called by name.

Request​

PUT /backups/backup.tar?partNumber=1&uploadId=EXAMPLE-UPLOAD-ID 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: UNSIGNED-PAYLOAD
Content-Length: 67108864

<67,108,864 bytes of part data>

Path-style, the same request is PUT /my-bucket/backups/backup.tar?partNumber=1&uploadId=EXAMPLE-UPLOAD-ID on s3.eu-central-1.strg.com - Addressing explains both forms.

Permissions​

Uploading a part takes Write objects (objects:write), the same permission that started the upload. For a key limited to certain buckets or to a key prefix, the bucket and the object key need to be within them. Access keys has every permission.

URI parameters​

NameTypeRequiredDescription
BucketStringYesThe bucket the upload was started in.
KeyStringYesThe object key the upload was started for. A key with a . or .. path segment (a/../b) cannot be put in a URL, and STRG answers 400 InvalidURI.
partNumberIntegerYesThe part's number, 1-10,000. Anything else is answered 400 InvalidArgument.
uploadIdStringYesThe upload ID CreateMultipartUpload returned, exactly as it came.

Any other query parameter is answered with 501 NotImplemented, so a client knows straight away. A request carrying x-amz-copy-source is UploadPartCopy rather than UploadPart.

Request headers​

NameTypeRequiredDescription
Content-LengthIntegerYesThe size of the body in bytes. A part can be up to 5 GiB. A body without it - Transfer-Encoding: chunked - is answered 411 MissingContentLength.
Content-MD5StringNoThe base64-encoded MD5 digest of the part, for an integrity check. A part that does not match is answered 400 BadDigest, and nothing of it is stored.
x-amz-sdk-checksum-algorithmStringNoThe checksum algorithm the SDK used, such as CRC32, sent beside the matching x-amz-checksum-* header or trailer. An upload started with x-amz-checksum-algorithm has every part carry a checksum of that algorithm.
x-amz-checksum-crc32StringNoBase64-encoded CRC32 checksum of the part.
x-amz-checksum-crc32cStringNoBase64-encoded CRC32C checksum of the part.
x-amz-checksum-crc64nvmeStringNoBase64-encoded CRC64NVME checksum of the part.
x-amz-checksum-sha1StringNoBase64-encoded SHA-1 digest of the part.
x-amz-checksum-sha256StringNoBase64-encoded SHA-256 digest of the part.
x-amz-checksum-sha512StringNoPassed on with the request. The five checksums above are the ones to reach for.
x-amz-checksum-md5StringNoPassed on with the request. Content-MD5 or one of the five checksums above covers the same need.
x-amz-checksum-xxhash64StringNoPassed on with the request. The five checksums above are the ones to reach for.
x-amz-checksum-xxhash3StringNoPassed on with the request. The five checksums above are the ones to reach for.
x-amz-checksum-xxhash128StringNoPassed on with the request. The five checksums above are the ones to reach for.
x-amz-server-side-encryption-customer-algorithmStringConditionalSSE-C: AES256. Needed on every part when the upload was started with SSE-C.
x-amz-server-side-encryption-customer-keyStringConditionalSSE-C: the same base64-encoded 256-bit key sent to CreateMultipartUpload.
x-amz-server-side-encryption-customer-key-MD5StringConditionalSSE-C: the base64-encoded MD5 digest of the key.
x-amz-expected-bucket-ownerStringNoWelcome, with nothing to check: an access key only ever reaches its own account's buckets.
x-amz-request-payerStringNoNot 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.

Streaming uploads work too: an aws-chunked body (STREAMING-AWS4-HMAC-SHA256-PAYLOAD, STREAMING-AWS4-HMAC-SHA256-PAYLOAD-TRAILER or STREAMING-UNSIGNED-PAYLOAD-TRAILER) with x-amz-decoded-content-length, and a trailing checksum declared in x-amz-trailer - CRC32, CRC32C, CRC64NVME, SHA-1 or SHA-256. STRG verifies every chunk signature and the trailing checksum, and stores nothing of a part that fails. Two combinations get a different answer:

RequestAnswer
A streaming payload in a presigned URL400 InvalidRequest
A SHA-1 or SHA-256 trailer with a Content-Encoding besides aws-chunked (aws-chunked,gzip)501 NotImplemented. A CRC trailer works with any encoding

The AWS SDKs' default upload behaviour works as it is; there is nothing to turn off.

Request body​

The part's bytes.

Response​

A successful request answers 200 OK with an empty body.

Response headers​

NameDescription
ETagThe part's entity tag. Together with the part number, it is what CompleteMultipartUpload needs.
x-amz-request-idThe request ID, handy for a support request.
x-amz-server-side-encryptionThe server-side encryption used, when the object is encrypted with STRG-managed keys (AES256).
x-amz-server-side-encryption-customer-algorithmFor SSE-C, the algorithm that was sent.
x-amz-server-side-encryption-customer-key-MD5For SSE-C, the MD5 digest of the key that was sent.
x-amz-checksum-crc32, x-amz-checksum-crc32c, x-amz-checksum-crc64nvme, x-amz-checksum-sha1, x-amz-checksum-sha256The part's checksum, when the request carried one.

x-amz-server-side-encryption-aws-kms-key-id, x-amz-server-side-encryption-bucket-key-enabled and x-amz-request-charged never appear: STRG has no SSE-KMS and no Requester Pays.

Response body​

None.

Errors​

These are the answers an UploadPart can get besides 200 OK, and the situation behind each:

CodeHTTP statusWhen
NoSuchUpload404The upload ID does not exist, or the upload was already completed or aborted.
NoSuchBucket404The bucket does not exist, it is in a region other than the endpoint's, or your account stores nothing in this endpoint's region.
InvalidArgument400partNumber is not an integer from 1 to 10,000.
InvalidURI400The key contains a . or .. path segment, such as a/../b. Nothing is stored.
EntityTooLarge400The declared part size is over 5 GiB.
MissingContentLength411The body has no Content-Length.
IncompleteBody400The body is shorter than Content-Length or x-amz-decoded-content-length declares.
BadDigest400The part does not match Content-MD5 or an x-amz-checksum-* value, sent as a header or as a trailer. Nothing of the part is stored.
InvalidRequest400An aws-chunked body is malformed, lacks x-amz-decoded-content-length or x-amz-trailer, carries a trailing checksum that is not a valid value, or is used in a presigned URL.
AccessDenied403The access key lacks Write objects, or the bucket or key is outside its buckets or prefix, or an x-amz-* header is not signed, or a presigned URL has expired.
NotImplemented501A query parameter this operation does not take, an ECDSA-signed aws-chunked body, a trailer other than the five listed above, or a SHA trailer with a combined Content-Encoding.
InvalidAccessKeyId403The 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 shortly afterwards.
SignatureDoesNotMatch403The request signature, or a chunk signature of an aws-chunked body, does not match.
RequestTimeTooSkewed403The request time is more than 15 minutes from the server's.
AuthorizationHeaderMalformed400The signature's credential scope names a region other than the endpoint's.
SlowDown503Too many requests at once. The response carries a Retry-After interval, and the AWS SDKs retry on their own.
ServiceUnavailable503A temporary failure; the same request can be retried.

Differences from Amazon S3​

Where STRG answers differently from Amazon S3, and what that means for a client:

AspectAmazon S3STRG
Part size5 MiB minimum (except the last part), 5 GiB maximum5,000,000 bytes minimum (except the last part), 5 GiB maximum - so a part size chosen for S3 works unchanged
Body without a lengthTransfer-Encoding: chunked accepted in some cases411 MissingContentLength. aws-chunked uploads work as they are
Streaming uploadsEvery aws-chunked variant, ECDSA signatures includedThe HMAC and unsigned variants, with trailing checksums CRC32, CRC32C, CRC64NVME, SHA-1 and SHA-256. A SHA trailer with a combined Content-Encoding is 501 NotImplemented; a CRC trailer works with any encoding
Bucket in another region301 redirect with x-amz-bucket-region404 NoSuchBucket. A bucket is reached through its own regional endpoint
x-amz-expected-bucket-ownerChecked against the bucket ownerNothing to check: a key reaches only its own account's buckets
Keys with a . or .. path segmentAccepted400 InvalidURI

Examples​

Each example uploads part 1 of an upload started with CreateMultipartUpload, whose upload ID is in the UPLOAD_ID environment variable, from a file part1.bin of at least 5,000,000 bytes - unless it is the last part - and at most 5 GiB.

Uploading a part​

aws s3api upload-part \
--bucket my-bucket \
--key backups/backup.tar \
--part-number 1 \
--upload-id "$UPLOAD_ID" \
--body ./part1.bin \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1 \
--query ETag \
--output text

For a whole file, the SDKs' transfer tools split it into parts and upload them for you - Uploading a large file on the CreateMultipartUpload page shows how.

Uploading a part through a presigned URL​

A presigned URL lets a client without credentials - a browser, a partner's script - upload one part with a plain PUT. The URL is valid for anything from 1 second to 7 days. The AWS CLI does not presign UploadPart, so the URL comes from an SDK and the upload is shown with curl.

Presigning with the JavaScript SDK

With its default settings, the JavaScript SDK signs the checksum of an empty body into a presigned upload URL, so real data sent through it is answered 400 BadDigest - on Amazon S3 as on STRG. A client created with requestChecksumCalculation: "WHEN_REQUIRED", as below, presigns URLs that work.

import os

import boto3

s3 = boto3.client(
"s3",
endpoint_url="https://s3.eu-central-1.strg.com",
region_name="eu-central-1",
)

url = s3.generate_presigned_url(
"upload_part",
Params={
"Bucket": "my-bucket",
"Key": "backups/backup.tar",
"PartNumber": 1,
"UploadId": os.environ["UPLOAD_ID"],
},
ExpiresIn=3600,
)
print(url)

The uploader then sends the part to the URL, here held in PRESIGNED_URL, and the ETag is in the response headers:

curl -i -X PUT --upload-file ./part1.bin "$PRESIGNED_URL"
View as Markdown