Skip to main content

PutObjectLegalHold

PutObjectLegalHold places a legal hold on an object version (ON) or lifts it (OFF). While a version is held, it cannot be deleted or overwritten, and it stays that way until the hold is lifted. A hold has no end date and is independent of any retention period, which makes it a good fit when nobody knows yet how long something needs to be kept. Legal holds exist in buckets with Object Lock enabled and are set per object version - there is no bucket default for them. GetObjectLegalHold reads a hold back.

Good to know on STRG
  • Each bucket is reached through its own regional endpoint, and a bucket in another region answers 404 NoSuchBucket rather than a redirect.
  • x-amz-expected-bucket-owner is welcome and has nothing to check: a key only ever reaches buckets of its own account.
  • The request body is sent in one piece: an aws-chunked body is answered 501 NotImplemented.
  • A key with a . or .. path segment (a/../b) cannot be put in a URL, so STRG answers 400 InvalidURI for it.

When you'd use it​

  • A dispute or an investigation. When a team is asked to preserve a set of contracts or emails, a script places a hold on each version involved, and lifts the holds once the matter is closed - however long that turns out to take.
  • Pausing a clean-up for one object. A bucket whose old versions a lifecycle rule or a nightly job removes can keep a single version safe from both by holding it, without changing the rule.
  • An agent keeping evidence. An agent asked to make sure a report is not deleted while it is being reviewed places a hold on that version, and the reviewer, or the agent later on, lifts it.

Request​

PUT /photos/2024/cat.jpg?legal-hold 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: <SHA-256 of the body>
Content-Type: application/xml
Content-MD5: <base64 MD5 of the body>

<?xml version="1.0" encoding="UTF-8"?>
<LegalHold xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Status>ON</Status>
</LegalHold>

The path-style form is PUT /my-bucket/photos/2024/cat.jpg?legal-hold on s3.eu-central-1.strg.com - Addressing explains both.

Permissions​

The access key needs Set legal holds (object-legal-hold:write). A key limited to certain buckets can hold it too, and then sets legal holds in those buckets; a key limited to a key prefix sets them for objects under that prefix. Access keys has the details.

URI parameters​

NameTypeRequiredDescription
BucketStringYesThe bucket holding the object.
KeyStringYesThe object's key. A key with a . or .. path segment (a/../b) is answered 400 InvalidURI.
legal-holdFlagYesSelects the legal hold subresource. Sent with no value.
versionIdStringNoThe version to hold or release. Without it, the current version.

A query parameter other than these is answered 501 NotImplemented, so a client knows straight away; presigning's X-Amz-* parameters and the JavaScript SDK's x-id are always welcome.

Request headers​

NameTypeRequiredDescription
Content-MD5StringNoBase64 MD5 of the body. When it is sent, STRG checks it, and a mismatch is answered 400 BadDigest. The SDKs compute an integrity header on their own.
x-amz-sdk-checksum-algorithmStringNoSet by the SDKs to name the checksum they send with the body.
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.

Request body​

An XML document of up to 2 MiB; a larger one is answered 400 MaxMessageLengthExceeded.

ElementTypeRequiredDescription
LegalHoldContainerYesThe root element.
StatusStringYesON to place the hold, OFF to lift it.

Response​

A successful request answers 200 OK with an empty body.

Response headers​

NameDescription
x-amz-request-idThe request ID, handy for a support request.
x-amz-request-chargedNot sent: STRG has no Requester Pays buckets.

Response body​

None.

Errors​

These are the errors this request can return, and the situation behind each one.

CodeHTTP statusWhen
NoSuchBucket404The bucket does not exist, it is in a region other than the endpoint's, or the account has no storage in this endpoint's region.
AccessDenied403The access key lacks Set legal holds, or the bucket or key is outside its buckets or prefix.
InvalidAccessKeyId403The access key does not exist, has expired or been revoked - or it was created moments ago and is not usable yet in a region where the account already stores data, in which case the same request works shortly afterwards.
InvalidURI400The key contains a . or .. path segment.
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.
BadDigest400Content-MD5 does not match the body.
XAmzContentSHA256Mismatch400x-amz-content-sha256 does not match the body.
MalformedXML400The body is not valid UTF-8.
MaxMessageLengthExceeded400The body is larger than 2 MiB.
NotImplemented501The request carries a query parameter other than legal-hold and versionId (presigning's X-Amz-* parameters and the JavaScript SDK's x-id aside), or an aws-chunked body.
SlowDown503Too many requests. The Retry-After header says when a retry is welcome; the AWS SDKs retry on their own.
ServiceUnavailable503A temporary failure; the same request can be retried.

In a bucket without Object Lock there are no legal holds to set, so the request answers with a 4xx error and changes nothing.

Differences from Amazon S3​

The table lists each place where STRG behaves differently, and what that means for a client.

AspectAmazon S3STRG
Bucket in another region301 redirect with x-amz-bucket-region404 NoSuchBucket. A bucket is reached through its own regional endpoint
Nothing stored in the endpoint's region yetAny valid key reaches the endpoint404 NoSuchBucket
x-amz-expected-bucket-ownerChecked against the bucket ownerWelcome, with nothing to check
aws-chunked request bodyAccepted501 NotImplemented: the body is sent in one piece
Keys with . or .. segmentsAllowed400 InvalidURI: such a key cannot be put in a URL, so its legal hold cannot be set
A held version stays, and stays billed

Nobody can delete a version under a legal hold until the hold is lifted, and until then it is billed as stored data. A hold that is no longer needed ends with Status OFF, after which the version can be deleted as usual - unless a retention period still covers it.

Examples​

Placing a legal hold on the current version of photos/2024/cat.jpg:

aws s3api put-object-legal-hold \
--bucket my-bucket \
--key photos/2024/cat.jpg \
--legal-hold Status=ON \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1

Lifting the legal hold on a specific version, whose ID ListObjectVersions lists:

aws s3api put-object-legal-hold \
--bucket my-bucket \
--key photos/2024/cat.jpg \
--version-id "$VERSION_ID" \
--legal-hold Status=OFF \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1

An object can also be uploaded already under a legal hold: PutObject, CopyObject and CreateMultipartUpload take x-amz-object-lock-legal-hold: ON, which needs Set legal holds as well.

View as Markdown