Buckets & Listing
CreateBucket, DeleteBucket, HeadBucket, ListBuckets, and both ListObjects generations — the container and enumeration layer every S3 client walks before it touches a byte of object data.
CreateBucket, DeleteBucket, HeadBucket, ListBuckets, and both ListObjects generations — the container and enumeration layer every S3 client walks before it touches a byte of object data.
What this area is
Listing is how a client discovers what’s inside one (or which buckets it owns) — the operations a client runs before it can address a single object.
Operations covered here
CreateBucket · DeleteBucket · HeadBucket · GetBucketLocation · ListBuckets · ListObjectsV1/V2 · ListObjectVersions · ListMultipartUploads
How XNS implements it
CreateBucket (bucket_handlers.go) checks a name against every principal’s partition on the box: a name already held elsewhere returns 409 BucketAlreadyExists; the owner’s own pre-existing bucket is an idempotent 200. A post-create configuration failure (object-lock, default-ACL, ownership) triggers an all-or-nothing teardown (compensatingDeleteBucket, BUG-306) — no half-configured bucket survives.
The gateway is cost_center-scoped, multi-tenant: bucket and object stores resolve by The BUG-280 disappearing-buckets fix (rel 2.3.1) is relevant here: a metadata-directory read used to leave a leading-slash artifact on certain bucket names, which then failed the name-format check and silently vanished from a page — now trimmed and normalized before validation, at both the listing site and the pagination-token decode site.(cost_center, …), and the box’s own primary account is remapped to legacy root ("") so pre-multitenancy buckets stay visible at the bucket root instead of moving under a per-owner folder (“owner-at-root”). A genuine second tenant keeps its own distinct partition. Requests land on the S3 data plane, port 9000 plain HTTP (9443 TLS additive), SigV4-signed.
bucket_cleanup.go), scoped to (cost_center, bucket) so one tenant's delete can never sweep another's rows (BUG-240).us-east-1 (region-agnostic gateway).ReadScope{IsOwner:true}); a genuine tenant's list stays scoped to its own cost_center.read_handlers.go.How we conform to the S3 protocol
Bucket and listing behavior — CRUD, per-box uniqueness enforcement, and both ListObjects generations — is verified against the same public conformance suite the rest of the industry is measured by, test by test. We publish the full per-test result, including what still fails, on the compatibility matrix rather than summarizing it here.
Where we deliberately differ. An anonymous or cross-principal caller listing a bucket without credentials gets a uniform AccessDenied, refused outright under the 2026-08 product-security tightening (BUG-361). That's a wider deny than the pre-hardening posture, not a compatibility gap.
The reasoning behind that posture is set out under “What we chose to be different about” on the compatibility matrix, alongside every measured figure and its provenance.
How we compare
Source: the “Buckets & listing” section of the S3 compatibility matrix, competitor cells from each vendor’s own documentation, researched 2026-06-14.
| Capability | XNS | AWS S3 | Ceph | MinIO | Wasabi | B2 | Storj |
|---|---|---|---|---|---|---|---|
| Create / Delete bucket | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| ListObjects v1 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ◐ encrypted keys break lexicographic order |
| ListObjects v2 | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ◐ partial |
| Prefix + delimiter | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ◐ partial |
| Buckets per account | Unlimited | 10,000 | 1,000 | Unlimited | 1,000 | 100 | 100 |
How applications use it
import boto3
s3 = boto3.client(
"s3",
endpoint_url="https://relayer.example.com", # or http://localhost:9000 for local/dev
aws_access_key_id="AKIA...",
aws_secret_access_key="...",
region_name="us-east-1",
)
s3.create_bucket(Bucket="my-bucket")
# List "directories" one level deep using delimiter
resp = s3.list_objects_v2(Bucket="my-bucket", Prefix="logs/2026-08/", Delimiter="/")
for cp in resp.get("CommonPrefixes", []):
print(cp["Prefix"])
# Paginate a full listing
paginator = s3.get_paginator("list_objects_v2")
for page in paginator.paginate(Bucket="my-bucket"):
for obj in page.get("Contents", []):
print(obj["Key"], obj["Size"])
# Which buckets do I own?
for b in s3.list_buckets()["Buckets"]:
print(b["Name"])aws s3api create-bucket --bucket my-bucket --endpoint-url https://relayer.example.com
aws s3 ls s3://my-bucket/logs/2026-08/ --endpoint-url https://relayer.example.com
aws s3api list-objects-v2 --bucket my-bucket --prefix logs/2026-08/ --delimiter / \
--endpoint-url https://relayer.example.com
aws s3api list-buckets --endpoint-url https://relayer.example.comUse cases
Each bucket represents a dataset or environment, enumerated via ListBuckets by an inventory job.
Organized by project/branch/build-id prefixes, browsed with prefix + delimiter instead of a full key dump.
logs/2026-08-12/… listed incrementally by day for a downstream ETL job.
HeadBucket before CreateBucket to make provisioning idempotent.
Applications that lean on it heavily
Both drive bucket sync and mirroring from repeated ListObjectsV2 calls; trailing-slash bucket-path normalization specifically targets these clients.
Provisions the state bucket with CreateBucket/HeadBucket before ever writing a state object.
Page through source buckets with ListObjectsV2 pagination to discover new files incrementally.
Build prefix-delimited “folder” views directly from CommonPrefixes in a ListObjectsV2 response.
What’s out of scope here
A name is only checked against other tenants' partitions on the same box — two separate XNS deployments can each have a bucket named backups with no collision, which is a different guarantee than AWS provides and matters if you're porting infrastructure code that assumes global uniqueness.
FAQ
How many buckets can one account create?
Unlimited on the XNS S3 Gateway — there is no fixed per-account bucket ceiling, unlike AWS (10,000), Ceph (1,000), Wasabi (1,000), B2 (100), or Storj (100).
Does ListObjectsV2 pagination work the same as AWS?
Yes, with continuation tokens, prefix, and delimiter all supported and conformance-tested. A 2026-07 fix (BUG-280) also corrected a case where legacy-format bucket names could silently vanish from a ListBuckets page after a metadata-directory normalization step.
Can I create a bucket with the same name another tenant already holds?
No. Bucket names are unique per box across all tenants (a per-box flat namespace) — a name already held by another principal’s partition returns 409 BucketAlreadyExists.
The buckets and listing rows above are one section of the full S3 compatibility matrix — object operations, versioning, lifecycle, encryption, access control, replication, and integration all get the same treatment.
Explore the platform
Everything you need to go from evaluation to production.