Supported APIs
LocalS3 routes every request to one of three kinds of handler: an operation it implements, an operation it knows and
answers 501 NotImplemented, or the fallback handler for everything else. The lists below are checked against the
routes of the router by SupportedApiDocumentationTest, so they can't drift from what the service answers.
For how the supported operations behave where Amazon S3 leaves room for interpretation, e.g. conditional requests and versioning, see semantics.md.
Many configurations below are stored and read back but never applied, e.g. notifications, replication and ACLs. Stored, not applied lists them in one table, with how each differs from Amazon S3.
Supported Amazon S3 APIs
- AbortMultipartUpload
- CopyObject
- CreateBucket
- CreateMultipartUpload
- CreateSession
- CompleteMultipartUpload
- DeleteBucket
- DeleteBucketAnalyticsConfiguration
- DeleteBucketCors
- DeleteBucketEncryption
- DeleteBucketIntelligentTieringConfiguration
- DeleteBucketInventoryConfiguration
- DeleteBucketLifecycle
- DeleteBucketMetricsConfiguration
- DeleteBucketOwnershipControls
- DeleteBucketPolicy
- DeleteBucketReplication
- DeleteBucketTagging
- DeleteBucketWebsite
- DeleteObject
- DeleteObjects
- DeleteObjectTagging
- GetObject
- GetObjectTagging
- GetObjectAcl
- PutObjectAcl
- GetObjectAttributes
- GetObjectLegalHold
- GetObjectLockConfiguration
- GetObjectRetention
- GetBucketAbac
- GetBucketAccelerateConfiguration
- GetBucketAcl
- GetBucketAnalyticsConfiguration
- GetBucketCors
- GetBucketEncryption
- GetBucketIntelligentTieringConfiguration
- GetBucketInventoryConfiguration
- GetBucketLifecycleConfiguration
- GetBucketLogging
- GetBucketMetricsConfiguration
- GetBucketNotificationConfiguration
- GetBucketOwnershipControls
- GetBucketPolicy
- GetBucketPolicyStatus
- GetBucketReplication
- GetBucketRequestPayment
- GetBucketVersioning
- GetBucketWebsite
- GetBucketTagging
- GetBucketLocation
- HeadBucket
- HeadObject
- ListBucketAnalyticsConfigurations
- ListBucketIntelligentTieringConfigurations
- ListBucketInventoryConfigurations
- ListBucketMetricsConfigurations
- ListBuckets
- ListDirectoryBuckets
- ListObjects
- ListObjectsV2
- ListObjectVersions
- ListMultipartUploads
- ListParts
- PutBucketAbac
- PutBucketAccelerateConfiguration
- PutBucketAcl
- PutBucketAnalyticsConfiguration
- PutBucketCors
- PutBucketEncryption
- PutBucketIntelligentTieringConfiguration
- PutBucketInventoryConfiguration
- PutBucketLifecycleConfiguration
- PutBucketLogging
- PutBucketMetricsConfiguration
- PutBucketNotificationConfiguration
- PutBucketOwnershipControls
- PutBucketPolicy
- PutBucketReplication
- PutBucketRequestPayment
- PutBucketVersioning
- PutBucketWebsite
- PutBucketTagging
- PostObject
- PutObject
- PutObjectTagging
- PutObjectLegalHold
- PutObjectLockConfiguration
- PutObjectRetention
- RenameObject
- RestoreObject
- UploadPart
- UploadPartCopy
- PutPublicAccessBlock
- GetPublicAccessBlock
- DeletePublicAccessBlock
- OPTIONS object (CORS preflight requests, answered without authentication)
The lifecycle configuration of a bucket is stored, and applied only when a test asks for it, with
POST /_admin/lifecycle or LocalS3#applyLifecycle(Instant), at a time of its choosing, e.g. 30 days from now, or on a
schedule that a long-running service turns on, e.g. LOCAL_S3_LIFECYCLE_INTERVAL=1h. See
semantics.md. The deprecated PutBucketLifecycle and GetBucketLifecycle
send the same requests, and are answered the same way.
The notification configuration of a bucket is stored and returned as it was put, but no event is sent to the
SNS topics, SQS queues, Lambda functions or EventBridge it names, and their ARNs aren't checked, so that code that
configures notifications when it starts, e.g. Terraform, CDK or an application, works against LocalS3. A bucket that
was never configured answers an empty NotificationConfiguration. To hear of changes, register a listener; see
change events. The deprecated PutBucketNotification and GetBucketNotification send
the same requests, and are answered the same way.
Transfer acceleration, access logging, requester pays, ownership controls and attribute-based access control (ABAC) are
stored and returned as they were put, but never applied: nothing is accelerated, logged or billed to the requester,
ACLs keep working whatever the object ownership, and no access is decided by the tags of a bucket. They exist so that
clients that read or write them, e.g.
Terraform refreshing an aws_s3_bucket, or CDK and SDK tool chains setting the ownership controls, work against
LocalS3. A bucket that was never configured answers like a new bucket of Amazon S3 does:
| Operation | Never configured | After the delete |
|---|---|---|
GetBucketAccelerateConfiguration | an empty AccelerateConfiguration | – |
GetBucketLogging | an empty BucketLoggingStatus | – |
GetBucketRequestPayment | <Payer>BucketOwner</Payer> | – |
GetBucketOwnershipControls | <ObjectOwnership>BucketOwnerEnforced</ObjectOwnership> | 404 OwnershipControlsNotFoundError |
GetBucketAbac | <Status>Disabled</Status> | – |
The analytics, intelligent-tiering, inventory and metrics configurations of a bucket, of which it has several, each
named by the id parameter, are stored and returned as they were put, but never applied either: no storage class
analysis runs, no object moves between access tiers, no inventory report is written and no CloudWatch metric is
published. They exist so that Terraform's aws_s3_bucket_metric, aws_s3_bucket_inventory,
aws_s3_bucket_analytics_configuration and aws_s3_bucket_intelligent_tiering_configuration, or CDK stacks that
declare them, work against LocalS3. A bucket has none until one is put; getting or deleting an id it doesn't have
answers 404 NoSuchConfiguration, and a PUT whose id isn't the <Id> of its document answers
400 InvalidArgument. The List… operations answer every configuration on one page, in the order of their IDs.
The website configuration of a bucket (PutBucketWebsite, GetBucketWebsite, DeleteBucketWebsite) is applied:
its index and error documents, redirects and routing rules decide how the bucket is served as a website on the port of
the S3 API, see semantics.md. A bucket that was never configured, or whose
configuration was deleted, answers GetBucketWebsite with 404 NoSuchWebsiteConfiguration.
Object Lock (x-amz-bucket-object-lock-enabled, the x-amz-object-lock-* headers of PutObject, CopyObject and
CreateMultipartUpload, retention and legal holds) protects object versions from being deleted, see
semantics.md.
RestoreObject of a GLACIER or DEEP_ARCHIVE object completes at once, and HeadObject answers the restored copy
with x-amz-restore, so that code that restores cold data before reading it works; see
semantics.md.
ListDirectoryBuckets lists the buckets named like S3 Express One Zone directory buckets, e.g.
my-bucket--usw2-az1--x-s3, paginated with max-directory-buckets (at most 1000) and continuation-token. It is a
GET / like ListBuckets, told apart by its max-directory-buckets parameter or by the s3express service that the
AWS SDKs sign it for. ListBuckets keeps listing every bucket, the directory buckets included.
RenameObject and appends (PutObject with x-amz-write-offset-bytes), which Amazon S3 offers for S3 Express One Zone
directory buckets, work on the buckets whose versioning was never enabled, see
semantics.md. A bucket named like a directory bucket, e.g.
my-bucket--usw2-az1--x-s3, is addressed by the AWS SDKs the S3 Express way, with the stateless session credentials
of CreateSession, see semantics.md.
Server-side encryption with customer-provided keys (SSE-C) is accepted and echoed, but nothing is encrypted, see
semantics.md.
The x-amz-server-side-encryption* headers of SSE-S3, SSE-KMS and DSSE-KMS are stored with the object and answered
like Amazon S3 answers them, but nothing is encrypted either, see
semantics.md.
PostObject is the upload of a file by an HTML form that a browser posts to a bucket, with its policy document and
signature; see semantics.md.
ListBuckets is paginated with max-buckets, continuation-token, prefix and bucket-region, so
listBucketsPaginator works. A request without them gets every bucket on one page, like Amazon S3 answers it; a
paginated one gets at most max-buckets (1 to 10000) per page. Buckets are listed in the order they were created,
and the token marks the last bucket of a page, so a bucket created or deleted between two pages neither repeats a
bucket nor skips one.
Besides the S3 API, a service answers a health check and a few admin endpoints; see deployment.md.
Supported Amazon S3 Vectors APIs
Vector Bucket Operations:
- CreateVectorBucket
- GetVectorBucket
- ListVectorBuckets
- DeleteVectorBucket
Vector Index Operations:
- CreateIndex
- GetIndex
- ListIndexes
- DeleteIndex
Vector Data Operations:
- PutVectors
- QueryVectors
- GetVectors
- ListVectors
- DeleteVectors
An index of the cosine distance metric rejects a zero vector — one whose values are all 0 — as AWS does, since it
has no direction: PutVectors and QueryVectors answer it with ValidationException.
A vector that contains NaN or an infinite value is rejected the same way by PutVectors and QueryVectors, for every
distance metric, since its distance to any vector is NaN.
PutVectors also applies the limits of AWS to each vector, with ValidationException: a key of at most 1024
characters, metadata of at most 40 KB, and filterable metadata — the metadata without the nonFilterableMetadataKeys
of the index — of at most 2 KB. A size is the one of the metadata written as JSON in UTF-8.
In the metadata filter of QueryVectors, every key of an object is a condition that must hold, $and and $or
included: {"genre": "drama", "$or": [{"year": 2020}, {"year": 2021}]} matches a drama of 2020 or 2021.
$gt, $gte, $lt and $lte take a number, and a filter that compares with anything else is rejected with
InvalidRequestException. A vector whose value of the field isn't a number — a string "2019", say — doesn't match the
comparison, as AWS does, instead of failing the query.
The whole filter is validated before any vector is compared, so a malformed one is rejected with
InvalidRequestException whatever the index holds, an empty index included, and however $and or $or would
short-circuit: the filter must be an object ({} filters nothing), an operator must be one of $eq, $ne, $gt,
$gte, $lt, $lte, $in, $nin, $exists, $and and $or, $and, $or, $in and $nin take a non-empty
array, $eq, $ne and the elements of $in and $nin take a single value, and $exists takes a boolean.
A key listed in the nonFilterableMetadataKeys of the index is not indexed for filtering, as on AWS: a filter treats it
as absent from every vector, so {"text": "hello"} matches none of them while {"text": {"$exists": false}} matches
all. Such a key is still stored and returned with returnMetadata.
ListVectors lists the vectors of an index in the order of their keys. Its nextToken marks the last key of the page
it answers, so a listing continues after that key however the vectors change between the pages: no vector that exists
for the whole listing is skipped or listed twice. With segmentCount and segmentIndex, a vector belongs to a segment
by a hash of its key, so it is listed by one segment only, whatever other vectors are put or deleted. A nextToken
that ListVectors didn't answer with is rejected with ValidationException, and so is a vector whose data can't be
read when returnData is set, with InternalServerException. GetVectors answers such a vector among the error keys
instead of with an empty data. Both read under the read lock of the vector bucket, as QueryVectors does, so a
concurrent PutVectors or DeleteVectors doesn't drop the data of a vector while it's being read.
ListVectorBuckets and ListIndexes list in the order of the names, and their nextToken marks the last name of the
page in the same way: a listing continues after that name however buckets or indexes are created or deleted between
the pages. The token isn't bound to prefix, so a prefix changed between pages continues after that name too.
A nextToken that they didn't answer with is rejected with ValidationException.
Vector Bucket Policy Operations:
- PutVectorBucketPolicy
- GetVectorBucketPolicy
- DeleteVectorBucketPolicy
Tagging Operations (of a vector bucket or an index, addressed by its ARN; CreateVectorBucket and CreateIndex accept tags too):
- TagResource
- UntagResource
- ListTagsForResource
Supported Amazon S3 Tables APIs
The Amazon S3 Tables API, which an
S3TablesClient of the AWS SDK and the s3-tables-catalog library of Iceberg speak. Every table bucket is also served
as an Iceberg REST catalog of its own, so an engine reaches the same tables by giving
a RESTCatalog the ARN of the table bucket as its warehouse.
A request of this API shares its paths with Amazon S3 — PUT /buckets is CreateTableBucket here and CreateBucket of
a bucket named buckets there — so it is told apart by the s3tables service in the credential scope of its signature.
A client that signs nothing reaches the API under /s3tables instead; see
the S3 Tables API.
Table Bucket Operations:
- CreateTableBucket
- GetTableBucket
- ListTableBuckets
- DeleteTableBucket
Namespace Operations:
- CreateNamespace
- GetNamespace
- ListNamespaces
- DeleteNamespace
Table Operations:
- CreateTable
- GetTable
- ListTables
- DeleteTable
- RenameTable
- GetTableMetadataLocation
- UpdateTableMetadataLocation
Tagging Operations (of a table bucket or a table, addressed by its ARN; CreateTableBucket and CreateTable accept tags too):
- TagResource
- UntagResource
- ListTagsForResource
Encryption and Storage Class Operations (stored and read back; nothing is encrypted or tiered, see semantics.md):
- PutTableBucketEncryption
- GetTableBucketEncryption
- DeleteTableBucketEncryption
- GetTableEncryption
- PutTableBucketStorageClass
- GetTableBucketStorageClass
- GetTableStorageClass
Resource Policy Operations (stored and read back; nothing is enforced):
- PutTableBucketPolicy
- GetTableBucketPolicy
- DeleteTableBucketPolicy
- PutTablePolicy
- GetTablePolicy
- DeleteTablePolicy
Maintenance and Metrics Operations (stored and read back; no job ever runs):
- PutTableBucketMaintenanceConfiguration
- GetTableBucketMaintenanceConfiguration
- PutTableMaintenanceConfiguration
- GetTableMaintenanceConfiguration
- GetTableMaintenanceJobStatus
- PutTableBucketMetricsConfiguration
- GetTableBucketMetricsConfiguration
- DeleteTableBucketMetricsConfiguration
Record Expiration Operations (stored and read back; no record ever expires):
- PutTableRecordExpirationConfiguration
- GetTableRecordExpirationConfiguration
- GetTableRecordExpirationJobStatus
Replication Operations (stored and read back; nothing is replicated):
- PutTableBucketReplication
- GetTableBucketReplication
- DeleteTableBucketReplication
- PutTableReplication
- GetTableReplication
- DeleteTableReplication
- GetTableReplicationStatus
Supported AWS STS APIs
A stateless STS endpoint on the same port issues temporary credentials of LocalS3, like the one of MinIO; see
embedding.md. STS requests are POST / with a form-urlencoded body.
- AssumeRole
- GetSessionToken
- GetCallerIdentity
Supported AWS KMS APIs
A stateless KMS endpoint on the same port wraps and unwraps data keys, so that a client which calls KMS before it
talks to S3, e.g. the Amazon S3 Encryption Client, runs against LocalS3; see
embedding.md. KMS requests are POST / with an X-Amz-Target: TrentService.<action> header and a JSON body. Nothing is really encrypted, see
semantics.md.
- GenerateDataKey
- GenerateDataKeyWithoutPlaintext
- Encrypt
- Decrypt
- DescribeKey
- GenerateRandom
Known unimplemented Amazon S3 APIs
LocalS3 is a mock for testing, so it implements the operations that application code exercises and leaves
the ones that configure a real bucket's billing, hosting and reporting alone. The operations below are
routed and answer 501 NotImplemented with an <Error> document naming the operation, so a client fails
with a clear error instead of appearing to succeed. If your tests need one of them, please
open an issue.
Object retrieval and transformation
- GetObjectTorrent
- SelectObjectContent
- WriteGetObjectResponse
Object encryption
- UpdateObjectEncryption
Object annotations
- DeleteObjectAnnotation
- GetObjectAnnotation
- ListObjectAnnotations
- PutObjectAnnotation
S3 Metadata (journal, inventory and annotation tables)
- CreateBucketMetadataConfiguration
- CreateBucketMetadataTableConfiguration
- DeleteBucketMetadataConfiguration
- DeleteBucketMetadataTableConfiguration
- GetBucketMetadataConfiguration
- GetBucketMetadataTableConfiguration
- UpdateBucketMetadataAnnotationTableConfiguration
- UpdateBucketMetadataInventoryTableConfiguration
- UpdateBucketMetadataJournalTableConfiguration
Not routed at all
Every other operation of the S3 API that isn't listed on this page has no route, and answers 501 NotImplemented
from the fallback handler, e.g. LocalS3 does not implement PATCH /<bucket>.
That includes a request that carries a subresource of Amazon S3, i.e. a query parameter that names an operation,
such as ?acl, ?cors, ?uploadId or ?metadataConfiguration, which no route of its method and path answers. Without
this rule, such a request would reach the operation that its method and path name without parameters, and appear to
succeed: GET /<bucket>?cors would be listed by ListObjects, PUT /<bucket>?location would create the bucket,
POST /<bucket>?tagging would be taken for a form upload, and DELETE /<bucket>/<key>?acl would delete the object.
A query parameter that names no operation, e.g. ?v=3 of a cache-busting link, is ignored, like Amazon S3 does. A CORS
preflight (OPTIONS) is answered whatever its parameters.
The S3 Control API (access points, batch operations, Storage Lens, …) isn't implemented either. Its requests, whose
paths start with /v20180820/ and which carry an x-amz-account-id or x-amz-outpost-id header or are signed for
s3-control, answer 501 NotImplemented with an error that names S3 Control, rather than NoSuchBucket of a bucket
named v20180820.
LocalS3SdkOperationsTest reads the HTTP binding of every operation of the S3Client of the AWS SDK for Java, and
checks that each one reaches its own route: an operation that the SDK adds is either implemented or listed above, never
answered by another operation.
GET /_admin/stats counts the requests answered 501 under notImplemented, by operation, e.g.
SelectObjectContent, or, for a request without a route, by its method, the shape of its path and its query
parameters, e.g. GET /{bucket}?analytics. It tells which operations your client needs that LocalS3 lacks.