Skip to main content

Object Operations

The Oyster S3 API supports these object operations:

  • PutObject: Uploads an object to a bucket
  • GetObject: Downloads an object's contents
  • HeadObject: Retrieves object metadata without downloading the contents
  • DeleteObject: Deletes an object from a bucket
  • ListObjectsV2: Lists objects in a bucket with optional filtering and pagination
  • Object tagging: Sets, reads, and removes an object's tags

All S3 operations require authentication (SigV4), including reads. See S3 Setup for configuration.

Authenticated reads: Unlike the JSON API, where blob reads are public and unauthenticated, all S3 reads require authentication.

PutObject​

Uploads an object to a bucket. If an object with the same key already exists, the upload replaces it. PutObject accepts single-part uploads up to 1GB (see Maximum object size).

aws --profile oyster s3api put-object \
--bucket my-bucket \
--key hello.txt \
--body hello.txt

Response:

{
"ETag": "\"9a0364b9e99bb480dd25e1f0284c8555\""
}

The ETag is the MD5 digest of the uploaded content.

How to set Content-Type​

aws --profile oyster s3api put-object \
--bucket my-bucket \
--key image.png \
--body photo.png \
--content-type "image/png"

If you omit --content-type, it defaults to application/octet-stream.

How to set tags on upload​

Attach tags at upload time with --tagging, a URL-encoded query string of key=value pairs:

aws --profile oyster s3api put-object \
--bucket my-bucket \
--key hello.txt \
--body hello.txt \
--tagging "env=prod&team=platform"

Tags set this way share the same store as the JSON API and the Object Tagging operations below, and are subject to the same tag rules.

Key behavior​

  • Overwrite: Uploading to an existing key replaces the object.
  • Expiration: Objects share the owning account's StoragePool lifetime; the background extension service renews the pool before it expires (see Blob Lifecycle).
  • Content-addressed: Identical content produces the same blob ID internally, enabling deduplication.

Maximum object size​

PutObject accepts single-part uploads only, up to a maximum of 1GB per object. Oyster does not support multipart upload, so you cannot split a larger object into parts. Because there is no multipart upload, the ETag is always the MD5 digest of the full object content, even for large objects.

Conditional headers​

PutObject supports If-Match and If-None-Match headers for safe writes:

  • If-None-Match: *: Upload only if the key doesn't already exist (create-only semantics). Returns 412 PreconditionFailed if the key exists.
  • If-Match: "<etag>": Overwrite only if the current object's ETag matches. Returns 412 PreconditionFailed on mismatch.
# Create-only: fail if the key already exists
aws --profile oyster s3api put-object \
--bucket my-bucket \
--key hello.txt \
--body hello.txt \
--if-none-match "*"

Errors:

S3 error codeCondition
NoSuchBucketBucket doesn't exist
PreconditionFailedIf-Match or If-None-Match condition not met

GetObject​

Downloads an object's contents.

aws --profile oyster s3api get-object \
--bucket my-bucket \
--key hello.txt \
downloaded.txt

Response metadata:

{
"ContentLength": 14,
"ContentType": "text/plain",
"ETag": "\"9a0364b9e99bb480dd25e1f0284c8555\"",
"LastModified": "2025-01-15T10:31:00Z"
}

The command writes the file contents to the output path (downloaded.txt in this example).

Conditional headers​

GetObject supports If-Match and If-None-Match for cache validation:

  • If-Match: "<etag>": Return the object only if its ETag matches. Returns 412 PreconditionFailed on mismatch.
  • If-None-Match: "<etag>": Return the object only if its ETag differs. Returns 304 NotModified if the ETag matches (useful for cache validation).
# Only download if the object has changed
aws --profile oyster s3api get-object \
--bucket my-bucket \
--key hello.txt \
--if-none-match '"9a0364b9e99bb480dd25e1f0284c8555"' \
downloaded.txt

Errors:

S3 error codeCondition
NoSuchBucketBucket doesn't exist
NoSuchKeyObject key doesn't exist
PreconditionFailedIf-Match condition not met
NotModifiedIf-None-Match matched; object unchanged (304)

HeadObject​

Retrieves object metadata without downloading the contents. Useful for checking if an object exists or reading its size and content type.

aws --profile oyster s3api head-object \
--bucket my-bucket \
--key hello.txt

Response:

{
"ContentLength": 14,
"ContentType": "text/plain",
"ETag": "\"9a0364b9e99bb480dd25e1f0284c8555\"",
"LastModified": "2025-01-15T10:31:00Z"
}

HeadObject supports the same If-Match and If-None-Match conditional headers as GetObject. Returns 412 PreconditionFailed or 304 NotModified as appropriate.

Errors:

S3 error codeCondition
NoSuchBucketBucket doesn't exist
NoSuchKeyObject key doesn't exist
PreconditionFailedIf-Match condition not met
NotModifiedIf-None-Match matched; object unchanged (304)

DeleteObject​

Deletes an object from a bucket.

aws --profile oyster s3api delete-object \
--bucket my-bucket \
--key hello.txt

Returns no output on success.

This operation is idempotent: deleting a key that doesn't exist still returns success, matching standard S3 behavior.

Deletion is reference-counted: Oyster removes the underlying blob data from storage only when no other keys reference the same content.

Conditional headers​

DeleteObject supports If-Match for safe deletion:

  • If-Match: "<etag>": Delete only if the object's ETag matches. Returns 412 PreconditionFailed on mismatch.

Errors:

S3 error codeCondition
NoSuchBucketBucket doesn't exist
PreconditionFailedIf-Match condition not met

ListObjectsV2​

Lists objects in a bucket with optional filtering and pagination.

Basic listing​

aws --profile oyster s3api list-objects-v2 --bucket my-bucket

Response:

{
"Name": "my-bucket",
"Contents": [
{
"Key": "hello.txt",
"Size": 14,
"ETag": "\"9a0364b9e99bb480...\"",
"LastModified": "2025-01-15T10:31:00Z",
"StorageClass": "STANDARD"
},
{
"Key": "images/photo.png",
"Size": 204800,
"ETag": "\"d41d8cd98f00b204...\"",
"LastModified": "2025-01-15T11:00:00Z",
"StorageClass": "STANDARD"
}
],
"KeyCount": 2,
"MaxKeys": 1000,
"IsTruncated": false
}

How to filter by prefix​

List only objects under a specific virtual folder:

aws --profile oyster s3api list-objects-v2 \
--bucket my-bucket \
--prefix "images/"

How to simulate folders with a delimiter​

Use --delimiter "/" to group objects into virtual folders:

aws --profile oyster s3api list-objects-v2 \
--bucket my-bucket \
--delimiter "/"

Response:

{
"Name": "my-bucket",
"Contents": [
{
"Key": "hello.txt",
"Size": 14,
"ETag": "\"9a0364b9e99bb480...\"",
"LastModified": "2025-01-15T10:31:00Z",
"StorageClass": "STANDARD"
}
],
"CommonPrefixes": [
{
"Prefix": "images/"
}
],
"KeyCount": 2,
"MaxKeys": 1000,
"Delimiter": "/",
"IsTruncated": false
}

Objects directly at the root level appear in Contents, while virtual folders (key prefixes before the delimiter) appear in CommonPrefixes.

How to combine prefix and delimiter​

List the contents of a specific virtual folder:

aws --profile oyster s3api list-objects-v2 \
--bucket my-bucket \
--prefix "images/" \
--delimiter "/"

How to paginate results​

Limit results and paginate through large listings:

# First page
aws --profile oyster s3api list-objects-v2 \
--bucket my-bucket \
--max-keys 10

# Next page (using NextContinuationToken from previous response)
aws --profile oyster s3api list-objects-v2 \
--bucket my-bucket \
--max-keys 10 \
--starting-token "last-key-from-previous-page"

Supported parameters​

ParameterAWS CLI flagDescription
Prefix--prefixFilter keys that start with this string
Delimiter--delimiterGroup keys by this separator (for example, /)
MaxKeys--max-keysMaximum objects to return (default: 1000)
StartAfter--start-afterReturn keys after this value (lexicographic)
ContinuationToken--starting-tokenContinue from a previous response

Errors:

S3 error codeCondition
NoSuchBucketBucket doesn't exist

Object tagging​

Oyster implements the three S3 object-tagging operations. Oyster stores tags in its database in the same blob_tags table that the JSON API tag endpoints use; a tag set through S3 is visible through the JSON API and vice versa. The same tag rules apply (maximum 10 tags; key ≤ 128 B; value ≤ 256 B; set ≤ 2048 B; restricted charset).

PutObjectTagging​

Replaces the object's entire tag set.

aws --profile oyster s3api put-object-tagging \
--bucket my-bucket \
--key hello.txt \
--tagging 'TagSet=[{Key=env,Value=prod},{Key=team,Value=platform}]'

GetObjectTagging​

Returns the object's current tags.

aws --profile oyster s3api get-object-tagging \
--bucket my-bucket \
--key hello.txt

Response:

{
"TagSet": [
{ "Key": "env", "Value": "prod" },
{ "Key": "team", "Value": "platform" }
]
}

DeleteObjectTagging​

Removes all tags from the object.

aws --profile oyster s3api delete-object-tagging \
--bucket my-bucket \
--key hello.txt

Errors:

S3 error codeCondition
NoSuchBucketBucket doesn't exist
NoSuchKeyObject key doesn't exist

DeleteObjectTagging on a missing key​

Unlike DeleteObject, DeleteObjectTagging is not idempotent for missing keys. If the object key doesn't exist, the operation returns NoSuchKey. If the bucket doesn't exist, it returns NoSuchBucket.