PutObjectRetention
PutObjectRetention sets the retention of an object version: its mode, governance or
compliance, and the date until which it is locked. Without versionId it sets the current
version's. Retention exists only in a bucket with Object Lock enabled,
and reading one back is the job of GetObjectRetention.
- Setting a retention takes the Set retention permission on the access key. Shortening, removing or changing a governance-mode lock takes Bypass governance retention as well.
x-amz-expected-bucket-owneris welcome and has nothing to check: a key only ever reaches buckets of its own account.- A key containing a
.or..path segment (a/../b) cannot be put in a URL, so STRG answers400 InvalidURIfor it. - The body is sent whole: an
aws-chunkedbody is answered with501 NotImplemented, and the AWS SDKs do not send this operation's body that way by default.
A locked object version cannot be deleted permanently until its retention date has passed, and it is billed as stored data until then. A compliance-mode lock cannot be shortened or removed by anyone - it can only be extended. Once set, that version is kept, and billed, until the date it names.
When you'd use it
- Keeping one backup longer. A bucket's default retention locks every new backup for a few weeks; the backup taken at the end of a quarter can have its lock extended to a year, so it outlasts the rest.
- Records that must be kept. A system archiving invoices or contracts locks each one in compliance mode until the end of the period the law asks for, so nobody - an administrator included - can remove it early.
- Protecting files after the fact. An object uploaded before anyone thought to lock it can be given a retention later, version by version.
- Correcting a mistake. A governance-mode lock set further out than intended can be shortened by a key holding Bypass governance retention - the one way back governance mode leaves open.
Request
PUT /photos/2024/cat.jpg?retention 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, hex>
Content-MD5: <MD5 of the body, base64>
Content-Type: application/xml
<?xml version="1.0" encoding="UTF-8"?>
<Retention xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Mode>GOVERNANCE</Mode>
<RetainUntilDate>2027-01-01T00:00:00.000Z</RetainUntilDate>
</Retention>
The path-style form is PUT /my-bucket/photos/2024/cat.jpg?retention on
s3.eu-central-1.strg.com, which a bucket name containing a dot needs -
Addressing explains both.
Permissions
Which permissions a request needs depends on the change it makes:
| Change | Permissions needed |
|---|---|
| Setting a retention on a version that has none, or extending one to a later date | Set retention (object-retention:write) |
| Moving a compliance-mode retention to a later date | Set retention (object-retention:write) |
| Shortening or removing a governance-mode retention, or changing it to compliance mode | Set retention and Bypass governance retention (object-retention:bypass-governance), with x-amz-bypass-governance-retention: true on the request |
| Shortening or removing a compliance-mode retention | No permission allows it: a compliance-mode retention only ever gets longer |
A request carrying x-amz-bypass-governance-retention: true from a key without Bypass
governance retention answers 403 AccessDenied, whether or not the change needed it. For a key
limited to certain buckets or to a key prefix, the bucket and the object key are within them.
Access keys has the details.
URI parameters
| Name | Type | Required | Description |
|---|---|---|---|
Bucket | String | Yes | The bucket holding the object. |
Key | String | Yes | The object key. A key containing a . or .. path segment answers 400 InvalidURI. |
retention | - | Yes | Selects this operation. Takes no value. |
versionId | String | No | The version whose retention to set; without it, the current version's. A version ID is an opaque string. |
Any other query parameter is answered with 501 NotImplemented, so a client knows straight
away. The X-Amz-* parameters of a presigned URL and the x-id parameter the JavaScript SDK
adds are welcome.
Request headers
| Name | Type | Required | Description |
|---|---|---|---|
x-amz-bypass-governance-retention | Boolean | No | true to shorten, remove or change a governance-mode retention. Takes Bypass governance retention on the access key. Has no effect on a compliance-mode retention. |
Content-MD5 | String | No | The base64 MD5 digest of the body. A digest that does not match the body answers 400 BadDigest. The AWS SDKs send an integrity header by default. |
x-amz-checksum-crc32, x-amz-checksum-crc32c, x-amz-checksum-crc64nvme, x-amz-checksum-sha1, x-amz-checksum-sha256 | String | No | A checksum of the body, accepted in place of Content-MD5. |
x-amz-sdk-checksum-algorithm | String | No | The algorithm of the x-amz-checksum-* header the SDK sends beside it. Accepted. |
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. |
Request body
A Retention document, UTF-8, up to 2 MiB, sent whole - an aws-chunked body is answered with
501 NotImplemented.
<?xml version="1.0" encoding="UTF-8"?>
<Retention xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Mode>COMPLIANCE</Mode>
<RetainUntilDate>2027-01-01T00:00:00.000Z</RetainUntilDate>
</Retention>
| Element | Type | Required | Description |
|---|---|---|---|
Retention | Container | Yes | The object version's new retention. |
Mode | String | No | GOVERNANCE or COMPLIANCE. How it works explains what each allows. |
RetainUntilDate | Timestamp | No | The date and time, in UTC and ISO 8601 form, until which the version cannot be deleted permanently. |
Response
A successful request answers 200 OK with no body.
Response headers
| Name | Description |
|---|---|
x-amz-request-id | The request ID, handy for a support request. |
x-amz-request-charged | Not returned: STRG has no Requester Pays buckets. |
Response body
None.
Errors
These are the errors this request can return, and the situation behind each one.
| Code | HTTP status | When |
|---|---|---|
NotImplemented | 501 | The request carries a query parameter other than retention and versionId (presigning's X-Amz-* parameters and x-id aside), or an aws-chunked body. |
NoSuchBucket | 404 | The 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. |
NoSuchKey | 404 | The object does not exist. |
AccessDenied | 403 | The access key lacks Set retention, or sends x-amz-bypass-governance-retention: true without Bypass governance retention, or the bucket or key is outside its buckets or prefix. |
InvalidAccessKeyId | 403 | The 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. |
InvalidURI | 400 | The key contains a . or .. path segment. |
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. |
BadDigest | 400 | Content-MD5 does not match the body. |
XAmzContentSHA256Mismatch | 400 | x-amz-content-sha256 does not match the body. |
MalformedXML | 400 | The body is not valid UTF-8, or not a valid Retention document. |
MaxMessageLengthExceeded | 400 | The body is larger than 2 MiB. |
SlowDown | 503 | Too many requests. The Retry-After header says when a retry is welcome; the AWS SDKs retry on their own. |
ServiceUnavailable | 503 | A temporary failure; the same request can be retried. |
InternalError | 500 | An unexpected failure; the same request can be retried. |
A request that would shorten or remove a compliance-mode retention, that would shorten, remove
or change a governance-mode retention without x-amz-bypass-governance-retention: true, or that
names an object in a bucket without Object Lock, answers a 4xx error and changes nothing - the
existing lock stays exactly as it was.
Differences from Amazon S3
The table lists each place where STRG behaves differently, and what that means for a client.
| Aspect | Amazon S3 | STRG |
|---|---|---|
| Permissions | s3:PutObjectRetention, plus s3:BypassGovernanceRetention to bypass | Set retention (object-retention:write), plus Bypass governance retention (object-retention:bypass-governance) on the access key |
x-amz-expected-bucket-owner | Checked against the bucket owner | Welcome, with nothing to check |
| Bucket in another region | 301 redirect with x-amz-bucket-region | 404 NoSuchBucket. A bucket is reached through its own regional endpoint |
| Nothing stored in the endpoint's region yet | Any valid key reaches the endpoint | 404 NoSuchBucket, since the account has no buckets there |
Keys with . or .. segments | Allowed | 400 InvalidURI, because such a key cannot be put in a URL |
aws-chunked request body | Accepted | 501 NotImplemented. The SDKs send this operation's body whole by default, so it does not come up in everyday use |
Examples
Locking the current version of photos/2024/cat.jpg in governance mode until 1 January 2027:
- AWS CLI
- Python (boto3)
- JavaScript (AWS SDK v3)
aws s3api put-object-retention \
--bucket my-bucket \
--key photos/2024/cat.jpg \
--retention '{"Mode":"GOVERNANCE","RetainUntilDate":"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",
)
s3.put_object_retention(
Bucket="my-bucket",
Key="photos/2024/cat.jpg",
Retention={
"Mode": "GOVERNANCE",
"RetainUntilDate": datetime(2027, 1, 1, tzinfo=timezone.utc),
},
)
import { PutObjectRetentionCommand, S3Client } from "@aws-sdk/client-s3";
const client = new S3Client({
endpoint: "https://s3.eu-central-1.strg.com",
region: "eu-central-1",
});
await client.send(
new PutObjectRetentionCommand({
Bucket: "my-bucket",
Key: "photos/2024/cat.jpg",
Retention: {
Mode: "GOVERNANCE",
RetainUntilDate: new Date("2027-01-01T00:00:00Z"),
},
}),
);
Shortening the governance-mode retention of a specific version, which takes Bypass governance
retention on the access key, with VERSION_ID set to a version ID from
ListObjectVersions:
- AWS CLI
- Python (boto3)
- JavaScript (AWS SDK v3)
aws s3api put-object-retention \
--bucket my-bucket \
--key photos/2024/cat.jpg \
--version-id "$VERSION_ID" \
--retention '{"Mode":"GOVERNANCE","RetainUntilDate":"2026-10-31T00:00:00Z"}' \
--bypass-governance-retention \
--endpoint-url https://s3.eu-central-1.strg.com \
--region eu-central-1
import os
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",
)
s3.put_object_retention(
Bucket="my-bucket",
Key="photos/2024/cat.jpg",
VersionId=os.environ["VERSION_ID"],
Retention={
"Mode": "GOVERNANCE",
"RetainUntilDate": datetime(2026, 10, 31, tzinfo=timezone.utc),
},
BypassGovernanceRetention=True,
)
import { PutObjectRetentionCommand, S3Client } from "@aws-sdk/client-s3";
const client = new S3Client({
endpoint: "https://s3.eu-central-1.strg.com",
region: "eu-central-1",
});
await client.send(
new PutObjectRetentionCommand({
Bucket: "my-bucket",
Key: "photos/2024/cat.jpg",
VersionId: process.env.VERSION_ID,
Retention: {
Mode: "GOVERNANCE",
RetainUntilDate: new Date("2026-10-31T00:00:00Z"),
},
BypassGovernanceRetention: true,
}),
);