Skip to main content

CopyObject

CopyObject makes a new object from one that is already stored, without the bytes ever leaving STRG - nothing is downloaded and uploaded again. The source can sit in the same bucket or in another bucket in the same region. It is the building block behind renaming an object (a copy, then a DeleteObject of the original), changing its metadata, and bringing back an earlier version as the current one.

For a large object, UploadPartCopy copies it in parts within a multipart upload.

Good to know on STRG
  • A copy stays within one region: the source and the destination are both buckets in the endpoint's region.
  • One access key covers both ends of the copy. It holds Read objects as well as Write objects, and its buckets and object prefix apply to the source just as they do to the destination.
  • x-amz-tagging, x-amz-checksum-algorithm, If-Match, If-None-Match, event holds, SSE-KMS and x-amz-grant-* get a 501 NotImplemented answer, so a client knows straight away.
  • x-amz-tagging-directive is welcome and has nothing to do: objects carry no tags.
  • x-amz-acl is private or public-read, matching the destination bucket - access is set per bucket, so every object in it shares the bucket's setting.
  • x-amz-expected-bucket-owner and x-amz-source-expected-bucket-owner are welcome and have nothing to check: a key only ever reaches buckets of its own account.
  • A destination key with a . or .. path segment (a/../b) is answered 400 InvalidURI, and nothing is stored.

When you'd use it​

  • Renaming and reorganizing. A photo app that receives uploads under incoming/ moves each one to photos/2024/ once it has been processed: a CopyObject to the new key, then a DeleteObject of the old one.
  • Fixing metadata. A static site's assets went up with the wrong Content-Type, or a longer Cache-Control would suit them better. Copying each object onto itself with x-amz-metadata-directive: REPLACE sets the new headers without re-uploading anything.
  • Bringing back an earlier version. Every bucket keeps the versions of its objects, so an agent that overwrote the notes file it keeps its memory in can copy yesterday's version onto the same key, and that version is current again.
  • Promoting a release. A deploy script copies a tested build from a private staging bucket into the public bucket its users download from, both in the same region, without the files passing through the CI machine.

Request​

PUT /photos/2024/cat-copy.jpg 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
x-amz-copy-source: /my-bucket/photos/2024/cat.jpg

Path-style, the same request is PUT /my-bucket/photos/2024/cat-copy.jpg on s3.eu-central-1.strg.com - Addressing has both styles.

The request has no body. Every x-amz-* header, x-amz-copy-source included, is part of the signature; the AWS CLI and SDKs take care of that.

Permissions​

A copy reads one object and writes another, so the access key needs both of these:

PermissionDashboard labelFor
objects:writeWrite objectsWriting the destination object
objects:readRead objectsReading the source object

For a key that reaches certain buckets or an object prefix, the destination bucket and key and the source bucket and key all fall within them; a copy reaching outside them is answered 403 AccessDenied.

Setting Object Lock on the copy takes one more permission per header:

HeaderAlso needs
x-amz-object-lock-mode or x-amz-object-lock-retain-until-dateSet retention (object-retention:write)
x-amz-object-lock-legal-holdSet legal holds (object-legal-hold:write)

Access keys has the details.

URI parameters​

NameTypeRequiredDescription
BucketStringYesThe destination bucket.
KeyStringYesThe destination object key. A key with a . or .. path segment is answered 400 InvalidURI.

CopyObject takes no query parameters. A query parameter S3 does not define for CopyObject gets a 501 NotImplemented answer.

Request headers​

The headers below are every one S3 defines for CopyObject, each with what STRG does with it.

NameTypeRequiredDescription
x-amz-copy-sourceStringYesThe source, as my-bucket/photos/2024/cat.jpg (a leading / is optional), the key URL-encoded. Appending ?versionId=VersionId copies that version; without it, the current version is copied. It travels as a header: in a presigned URL's query string it is answered 501 NotImplemented. A value that does not name a valid bucket and a non-empty key is answered 400 InvalidArgument.
x-amz-metadata-directiveStringNoCOPY (the default) keeps the source's metadata and content headers; REPLACE takes the ones in this request instead.
x-amz-meta-*StringNoUser metadata for the copy. Applied with x-amz-metadata-directive: REPLACE.
Cache-ControlStringNoStored with the copy. Applied with REPLACE.
Content-DispositionStringNoStored with the copy. Applied with REPLACE.
Content-EncodingStringNoStored with the copy. Applied with REPLACE.
Content-LanguageStringNoStored with the copy. Applied with REPLACE.
Content-TypeStringNoStored with the copy. Applied with REPLACE.
ExpiresTimestampNoStored with the copy. Applied with REPLACE.
x-amz-copy-source-if-matchStringNoCopies only if the source's ETag matches. STRG passes it along with the copy, and does not guarantee the outcome matches Amazon S3's.
x-amz-copy-source-if-none-matchStringNoCopies only if the source's ETag differs. STRG passes it along with the copy, and does not guarantee the outcome matches Amazon S3's.
x-amz-copy-source-if-modified-sinceTimestampNoCopies only if the source changed after this time. STRG passes it along with the copy, and does not guarantee the outcome matches Amazon S3's.
x-amz-copy-source-if-unmodified-sinceTimestampNoCopies only if the source has not changed since this time. STRG passes it along with the copy, and does not guarantee the outcome matches Amazon S3's.
If-MatchStringNoSTRG answers 501 NotImplemented.
If-None-MatchStringNoSTRG answers 501 NotImplemented.
x-amz-aclStringNoprivate or public-read, matching the destination bucket's Bucket Protection (private for a protected bucket). Any other canned ACL is answered 501 NotImplemented. Access is set per bucket, never per object.
x-amz-grant-full-controlStringNoSTRG answers 501 NotImplemented.
x-amz-grant-readStringNoSTRG answers 501 NotImplemented.
x-amz-grant-read-acpStringNoSTRG answers 501 NotImplemented.
x-amz-grant-write-acpStringNoSTRG answers 501 NotImplemented.
x-amz-taggingStringNoSTRG answers 501 NotImplemented: objects carry no tags.
x-amz-tagging-directiveStringNoWelcome, whatever its value, and has nothing to do: objects carry no tags.
x-amz-object-annotation-directiveStringNoSTRG answers 501 NotImplemented.
x-amz-checksum-algorithmStringNoSTRG answers 501 NotImplemented. The copy keeps the source's checksum algorithm, if it has one.
x-amz-server-side-encryptionStringNoAES256 - server-side encryption with STRG-managed keys. aws:kms and aws:kms:dsse are answered 501 NotImplemented.
x-amz-server-side-encryption-aws-kms-key-idStringNoSTRG answers 501 NotImplemented.
x-amz-server-side-encryption-contextStringNoSTRG answers 501 NotImplemented.
x-amz-server-side-encryption-bucket-key-enabledBooleanNoSTRG answers 501 NotImplemented.
x-amz-server-side-encryption-customer-algorithmStringNoAES256, to encrypt the copy with your own key (SSE-C).
x-amz-server-side-encryption-customer-keyStringNoThe base64 key to encrypt the copy with (SSE-C).
x-amz-server-side-encryption-customer-key-MD5StringNoThe base64 MD5 digest of that key.
x-amz-copy-source-server-side-encryption-customer-algorithmStringNoAES256, when the source is encrypted with your own key.
x-amz-copy-source-server-side-encryption-customer-keyStringNoThe key the source was encrypted with.
x-amz-copy-source-server-side-encryption-customer-key-MD5StringNoThe base64 MD5 digest of that key.
x-amz-object-lock-modeStringNoGOVERNANCE or COMPLIANCE, for a destination bucket with Object Lock enabled. Needs Set retention. Object Lock explains the modes.
x-amz-object-lock-retain-until-dateTimestampNoWhen the copy's retention ends. Needs Set retention.
x-amz-object-lock-legal-holdStringNoON or OFF. Needs Set legal holds.
x-amz-object-lock-event-holdStringNoSTRG answers 501 NotImplemented.
x-amz-object-lock-event-hold-duration-daysIntegerNoSTRG answers 501 NotImplemented.
x-amz-object-lock-event-hold-duration-yearsIntegerNoSTRG answers 501 NotImplemented.
x-amz-storage-classStringNoSTRG has one storage class, STANDARD.
x-amz-website-redirect-locationStringNoSTRG does not host bucket websites, so this header has no effect to count on.
x-amz-request-payerStringNoHas nothing to do: STRG has no Requester Pays buckets.
x-amz-expected-bucket-ownerStringNoWelcome, and has nothing to check: an access key only ever reaches its own account's buckets.
x-amz-source-expected-bucket-ownerStringNoWelcome, and has nothing to check, as above.

Request body​

None.

Response​

A successful copy answers 200 OK with a CopyObjectResult document.

Response headers​

NameDescription
x-amz-version-idThe version ID of the new copy. Buckets are always versioned, so every copy creates a new version.
x-amz-copy-source-version-idThe version ID of the source that was copied.
x-amz-server-side-encryptionAES256 when the copy is encrypted with STRG-managed keys.
x-amz-server-side-encryption-customer-algorithmAES256 when the copy is encrypted with your own key.
x-amz-server-side-encryption-customer-key-MD5The MD5 digest of your key, for SSE-C.
x-amz-request-idThe request ID, handy when contacting support.

Version IDs are opaque strings, best stored and sent back exactly as they are.

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 not part of the answer, and x-amz-expiration is not something to count on.

Response body​

<?xml version="1.0" encoding="UTF-8"?>
<CopyObjectResult>
<ETag>"85f30635602dc09bd85957a6e82a2c21"</ETag>
<LastModified>2026-09-26T12:00:00.000Z</LastModified>
</CopyObjectResult>
ElementDescription
ETagThe entity tag of the new copy.
LastModifiedWhen the copy was created.

Errors​

These are the answers a copy can get instead of 200 OK, and the situation behind each.

CodeHTTP statusWhen
NoSuchKey404The source object, or the version named by versionId, does not exist.
NoSuchBucket404The destination bucket does not exist, or it is in a region other than the endpoint's.
InvalidArgument400x-amz-copy-source does not name a valid bucket and key.
InvalidURI400The destination key contains a . or .. path segment, such as a/../b. Nothing is stored.
NotImplemented501The request carries a header or query parameter STRG does not offer - Request headers lists each one.
AccessDenied403The access key lacks Write objects or Read objects, or the Object Lock permission a lock header needs, or the source or destination is outside its buckets or object prefix. Also an expired presigned URL, and an unsigned x-amz-* header.
InvalidAccessKeyId403The access key does not exist, has expired or been revoked, or was created moments ago and is not yet usable in this region, where you already store data. It becomes usable shortly after.
SignatureDoesNotMatch403The request signature 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 AWS CLI and SDKs retry after the Retry-After interval.
ServiceUnavailable503A temporary failure, which a retry of the request gets past.

Differences from Amazon S3​

Each row is something STRG does its own way, and what that means for a copy.

AspectAmazon S3STRG
Source in another regionSupportedA copy stays within one region. Moving an object between regions is a download from one regional endpoint and an upload to the other.
Bucket in another region301 redirect with x-amz-bucket-region404 NoSuchBucket. The bucket's own regional endpoint reaches it.
Permissionss3:GetObject on the source, s3:PutObject on the destinationOne access key holding both Read objects and Write objects; its buckets and object prefix apply to the source and the destination
Tagsx-amz-tagging and x-amz-tagging-directive copy or replace the tag setObjects carry no tags: x-amz-tagging is answered 501, and x-amz-tagging-directive has nothing to do
Checksum algorithmx-amz-checksum-algorithm can change it501 NotImplemented. The copy keeps the source's checksum
If-Match / If-None-Match on the destinationSupported501 NotImplemented
ACLsAny canned ACL and x-amz-grant-*private or public-read, matching the destination bucket; grants are answered 501
SSE-KMSSupported501 NotImplemented. AES256 and SSE-C both encrypt the copy
Event holds, annotationsSupported501 NotImplemented
x-amz-copy-source in a presigned URL's queryAccepted501 NotImplemented. It travels as a header
x-amz-expected-bucket-owner, x-amz-source-expected-bucket-ownerChecked against the bucket ownerWelcome, with nothing to check
Keys with a . or .. path segment (a/../b)Supported400 InvalidURI as the destination key; nothing is stored

Examples​

Copying photos/2024/cat.jpg to photos/2024/cat-copy.jpg in the same bucket:

aws s3api copy-object \
--bucket my-bucket \
--key photos/2024/cat-copy.jpg \
--copy-source my-bucket/photos/2024/cat.jpg \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1

Changing an object's metadata in place is a copy onto itself with REPLACE:

aws s3api copy-object \
--bucket my-bucket \
--key photos/2024/cat.jpg \
--copy-source my-bucket/photos/2024/cat.jpg \
--metadata-directive REPLACE \
--content-type image/jpeg \
--cache-control "max-age=86400" \
--metadata photographer=alex \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1

Bringing back an earlier version as the current one is a copy of that version onto the same key. ListObjectVersions lists the version IDs:

aws s3api copy-object \
--bucket my-bucket \
--key photos/2024/cat.jpg \
--copy-source "my-bucket/photos/2024/cat.jpg?versionId=$VERSION_ID" \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1
View as Markdown