Object Operations

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, it is replaced.

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.

Setting Content-Type

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

If --content-type is omitted, it defaults to application/octet-stream.

Setting 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

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 / 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 file contents are written 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: the underlying blob data is only removed from storage 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
}

Filtering by Prefix

List only objects under a specific "folder":

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

Simulating Folders with 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 "folders" (key prefixes before the delimiter) appear in CommonPrefixes.

Combining Prefix and Delimiter

List the contents of a specific "folder":

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

Pagination

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-keysMax 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. Tags are stored in Oyster's database in the same blob_tags table used by the JSON API tag endpoints; a tag set through S3 is visible through the JSON API and vice versa. The same tag rules apply (max 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