Skip to main content

Creating a bucket

Go to Storage → Buckets and choose Create Bucket. Bucket Name is the only thing you have to fill in; Versioning, Default encryption, Deletion protection and Tags are on the same form.Those extras are applied as separate calls once the bucket exists, so the bucket is created even if one of them fails, and the console tells you which one did.
Bucket names follow the S3 rules, and they are checked in full:
3–63 characters
required
Lowercase letters, digits and hyphens. Must start and end with a letter or digit, must not contain a double hyphen (--), and must not be shaped like an IP address.
A bucket name is unique across the whole region, not just your account. A name another account already holds comes back 409. Creating a bucket you already own is a no-op that succeeds, so a repeated create is safe.
Bucket count is capped by your organization’s buckets quota; exhausting it is also a 409 on create.

Object Lock has to be decided here

Object Lock can only be enabled at creation. There is no call that turns it on later — you would have to create a new bucket and copy the objects across. Enabling it also turns versioning on, because a lock has nothing to hold onto without versions.
PUT /v1/buckets/{bucket}/object-lock updates the default retention rule on a bucket that already has Object Lock enabled. Against a bucket that does not, it is a 409 — object lock must be enabled at bucket creation.
Turning Object Lock on is API only. It has to ride on the call that creates the bucket, and the console’s Create Bucket form does not send it — the bucket is created without Object Lock, and the follow-up configuration is refused with that same 409. Create the bucket through the API when you need Object Lock.On a bucket that already has it, the console does edit the rule: the Object Lock card on the bucket’s Settings tab carries the Default retention rule, with a Mode and a Retention period.

Bucket settings in the native API

The settings endpoints use the same top-level fields for GET responses and PUT requests: The rules arrays have no extra configuration wrapper. Object Lock’s object_lock_rule contains default_retention. Lifecycle updates require the current revision in If-Match; see Lifecycle rules. These shapes apply to the native API. S3 clients continue to use the standard S3 configuration formats.

Pointing an S3 client at it

Set a custom endpoint and sign with your Basaltic access key. Nothing else about the client changes.
The same access keys you use everywhere else. A service account’s long-lived key needs nothing extra; temporary credentials from STS — a role session or a user session — must also carry the session token, and are rejected without it.See authentication for how to obtain each.
Both styles work. Virtual-hosted (https://my-app-assets.objects.sa-saopaulo-1.basaltic.cloud/key) is what most SDKs default to; path-style (https://objects.sa-saopaulo-1.basaltic.cloud/my-app-assets/key) is available through your client’s addressing-style option.
Set region_name to the Basaltic region code. The value is not checked against the region serving the request — it only has to match what your client signed with — so a tool hard-wired to us-east-1 still works. GetBucketLocation reports the real region.
A signed request must be within 15 minutes of the server’s clock, or it is rejected as too skewed. Presigned URLs are supported with an expiry between 1 second and 7 days, and a URL dated in the future beyond the skew tolerance is refused rather than becoming valid later.

What the S3 endpoint serves

The endpoint is verified against a real AWS SDK rather than a specification of our own — if boto3 can do it and gets S3’s error codes back, it works. What is routed today:

Buckets

ListBuckets, CreateBucket, HeadBucket, DeleteBucket, GetBucketLocation, and the ?policy, ?cors, ?lifecycle, ?versioning, ?encryption, ?tagging, ?object-lock and ?acl sub-resources.

Objects

PutObject, GetObject (including range requests), HeadObject, DeleteObject, DeleteObjects, CopyObject, ListObjects, ListObjectsV2, ListObjectVersions, and the ?tagging, ?retention, ?legal-hold and ?acl sub-resources.

Multipart

CreateMultipartUpload, UploadPart, UploadPartCopy, ListParts, ListMultipartUploads, CompleteMultipartUpload, AbortMultipartUpload.

Payload signing

Signed payloads, UNSIGNED-PAYLOAD, and signed aws-chunked streaming uploads. The body is re-hashed as it streams, so a body that does not match what was signed is rejected mid-flight.
Anything outside that list answers NotImplemented. Preflight OPTIONS requests are answered without a signature, because browsers never sign them.

Deleting a bucket

DELETE /v1/buckets/{bucket} does two quite different things depending on whether deletion protection is on. Protection is deliberately off by default for S3 parity: unlike Secrets and KMS, unprotected buckets have no recovery window.
The bucket must be empty. Any remaining objects, versions or in-flight multipart uploads make it a 409 BucketNotEmpty. An empty bucket is deleted immediately and the quota slot is released.
The Deletion protection card on the bucket’s Settings tab is a switch alongside Recovery window (days). Save applies both.
recovery_window_days accepts whole numbers from 1–30. Omitting every window field uses 7 days. Invalid values return 400, including when turning protection off.

Release note: recovery fields

Bucket responses expose recovery_window_days, scheduled_purge_at and nullable deleted_at. Protected DELETE returns 200 with scheduled_purge_at; immediate DELETE returns 204.

Breaking behavior change: invalid windows return 400

The service previously silently clamped windows: values below 1 selected 7 days, and values above 30 selected 30 days. It now rejects them with 400 instead. For example, asking for 60 days fails; it no longer schedules a purge after only 30 days. Explicit zero is also invalid; omit the window fields to select the 7-day default. Fractional days are invalid. Update callers that relied on clamping separately from adopting the new field names. During a rolling deployment, old service instances can still apply their old clamping behavior; rejection is universal once all instances are updated.