> ## Documentation Index
> Fetch the complete documentation index at: https://docs.basaltic.sh/llms.txt
> Use this file to discover all available pages before exploring further.

# Object storage

> Create buckets, configure them, and talk to them with any S3 client.

Object storage gives you buckets of keys and bytes, reachable two ways. You
**manage** a bucket — create it, set its policy, lifecycle, versioning and
encryption — on the regional storage API. You **read and write objects** with
any AWS S3 client, pointed at a separate S3-compatible endpoint.

<Columns cols={2}>
  <Card title="Bucket management" icon="settings">
    `https://storage.<region>.basaltic.sh`

    Signed with the Basaltic scheme, like every other Basaltic API. Buckets and
    their sub-resources live under `/v1/buckets`.
  </Card>

  <Card title="S3 wire protocol" icon="boxes">
    `https://objects.<region>.basaltic.cloud`

    Signed as S3 (AWS SigV4). This is what you give to boto3, the AWS CLI, or
    anything else that speaks S3.
  </Card>
</Columns>

<Note>
  The S3 endpoint is deliberately on a different domain — `basaltic.cloud`, not
  the product domain. Buckets serve content that gets embedded in other
  people's sites, so that traffic is kept off the domain your console session
  and API credentials belong to. Nothing you serve from a bucket can ride a
  product-domain cookie.
</Note>

Both surfaces are the same storage. A bucket created through the storage API is
immediately visible to an S3 client, and an object written by boto3 is
immediately readable through `GET /v1/buckets/{bucket}/objects/{key}`.

## Creating a bucket

<Tabs>
  <Tab title="Console">
    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.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    POST https://storage.sa-saopaulo-1.basaltic.sh/v1/buckets
    { "name": "my-app-assets" }
    ```
  </Tab>
</Tabs>

Bucket names follow the S3 rules, and they are checked in full:

<ResponseField name="name" type="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.
</ResponseField>

<Warning>
  **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.
</Warning>

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

```bash theme={null}
POST /v1/buckets
{ "name": "audit-archive", "object_lock_enabled": true }
```

<Warning>
  **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.
</Warning>

`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`.

<Note>
  **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**.
</Note>

## Pointing an S3 client at it

Set a custom endpoint and sign with your Basaltic access key. Nothing else about
the client changes.

<CodeGroup>
  ```python boto3 theme={null}
  import boto3
  from botocore.config import Config

  s3 = boto3.client(
      "s3",
      endpoint_url="https://objects.sa-saopaulo-1.basaltic.cloud",
      aws_access_key_id=ACCESS_KEY_ID,
      aws_secret_access_key=SECRET_ACCESS_KEY,
      region_name="sa-saopaulo-1",
      config=Config(signature_version="s3v4"),
  )

  s3.put_object(Bucket="my-app-assets", Key="images/logo.png", Body=data)
  ```

  ```bash AWS CLI theme={null}
  aws --endpoint-url https://objects.sa-saopaulo-1.basaltic.cloud \
      s3 cp ./logo.png s3://my-app-assets/images/logo.png
  ```

  ```python Temporary credentials theme={null}
  s3 = boto3.client(
      "s3",
      endpoint_url="https://objects.sa-saopaulo-1.basaltic.cloud",
      aws_access_key_id=creds.access_key_id,
      aws_secret_access_key=creds.secret_access_key,
      aws_session_token=creds.session_token,   # required for STS credentials
      region_name="sa-saopaulo-1",
      config=Config(signature_version="s3v4"),
  )
  ```
</CodeGroup>

<AccordionGroup>
  <Accordion title="Credentials" icon="key-round">
    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](/authentication) for how to obtain each.
  </Accordion>

  <Accordion title="Addressing style" icon="route">
    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.
  </Accordion>

  <Accordion title="The region string" icon="globe">
    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.
  </Accordion>

  <Accordion title="Clock skew and presigned URLs" icon="clock">
    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.
  </Accordion>
</AccordionGroup>

### 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:

<Columns cols={2}>
  <Card title="Buckets" icon="boxes">
    ListBuckets, CreateBucket, HeadBucket, DeleteBucket, GetBucketLocation, and
    the `?policy`, `?cors`, `?lifecycle`, `?versioning`, `?encryption`,
    `?tagging`, `?object-lock` and `?acl` sub-resources.
  </Card>

  <Card title="Objects" icon="file">
    PutObject, GetObject (including range requests), HeadObject, DeleteObject,
    DeleteObjects, CopyObject, ListObjects, ListObjectsV2, ListObjectVersions,
    and the `?tagging`, `?retention`, `?legal-hold` and `?acl` sub-resources.
  </Card>

  <Card title="Multipart" icon="layers">
    CreateMultipartUpload, UploadPart, UploadPartCopy, ListParts,
    ListMultipartUploads, CompleteMultipartUpload, AbortMultipartUpload.
  </Card>

  <Card title="Payload signing" icon="shield">
    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.
  </Card>
</Columns>

Anything outside that list answers `NotImplemented`. Preflight `OPTIONS`
requests are answered without a signature, because browsers never sign them.

## Objects and their limits

These are S3's own numbers, and they are enforced at S3's values:

| Limit                 | Value                        |
| --------------------- | ---------------------------- |
| Single-request upload | 5 GiB                        |
| Part size             | 5 MiB minimum, 5 GiB maximum |
| Parts per upload      | 10,000                       |

<Warning>
  The single-upload ceiling is checked against the size you **declare**, before
  any payload is read. An oversized `Content-Length` is refused with
  `EntityTooLarge` (`413` on the storage API) without transferring a byte — and
  a body that streams past the limit without declaring it is cut off too.
</Warning>

The 5 MiB floor applies to every part except the last one named at completion,
and it is checked **at completion**, not when the part is uploaded. A part list
whose non-final part is short fails with `EntityTooSmall` after the bytes are
already staged.

<Info>
  The assembled object is written out in full inside the request that completes
  the upload. Budget roughly **85 MiB/s** for that write, and note that the
  public edge ends any request after **one hour**. Objects beyond a few hundred
  GiB need a completion path this API does not offer yet.
</Info>

Uploads you start and never finish keep their staged parts until something
removes them. Add an `abort_incomplete_multipart_upload` [lifecycle
rule](#lifecycle-rules) rather than relying on remembering.

## Storage classes

Two classes, and they name device tiers rather than access patterns:

| Class      | Backed by                      | Use for                                  |
| ---------- | ------------------------------ | ---------------------------------------- |
| `STANDARD` | Solid-state pool. The default. | Anything served or read regularly.       |
| `COLD`     | Spinning-disk pool.            | Archives, backups, anything read rarely. |

They keep S3's uppercase spelling because the vocabulary is S3's. Set a class
per object with `x-amz-storage-class` on the upload, or move objects between
classes with a lifecycle transition. **The two classes bill separately**, so the
label an object carries decides which counter and which price its bytes land on.

An unsupported class — `STANDARD_IA`, `GLACIER`, anything else S3 defines — is
rejected rather than quietly stored as `STANDARD`.

## Versioning

<Tabs>
  <Tab title="Console">
    The **Versioning** card on the bucket's **Settings** tab shows the current
    state and offers **Enable** and **Suspend**.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    PUT /v1/buckets/{bucket}/versioning
    { "status": "enabled" }
    ```

    `status` takes `enabled` or `suspended` only. There is no way back to
    `disabled` — that state means a bucket that never had versioning, not one
    that had it turned off.
  </Tab>
</Tabs>

A bucket sits in one of three states:

<ResponseField name="disabled" type="never configured">
  The bucket has no versioning history. On the S3 wire this is reported by
  omitting the status element entirely, which is how S3 itself reports it.
</ResponseField>

<ResponseField name="enabled" type="every write creates a version">
  Deletes write a delete marker instead of removing bytes. `GET
      /v1/buckets/{bucket}/object-versions` lists versions and delete markers
  together.
</ResponseField>

<ResponseField name="suspended" type="was on, now off">
  **Existing versions are kept**; new writes stop creating them. This is
  distinct from `disabled`, and the distinction matters: suspending does not
  delete history.
</ResponseField>

The storage API uses the platform's lowercase vocabulary; the S3 endpoint spells
the same states `Enabled` and `Suspended` in its XML. Either spelling is
accepted on input.

<Warning>
  Versions you no longer need are not free — they count against your stored
  bytes. Pair versioning with a `noncurrent_version_expiration` lifecycle rule,
  or a suspended bucket quietly keeps every version it ever made.
</Warning>

## Object Lock and retention

On a bucket created with `object_lock_enabled`, individual objects can carry a
retention mode and a retain-until date, set at upload with
`X-Amz-Object-Lock-Mode` and `X-Amz-Object-Lock-Retain-Until-Date`, or afterwards
through the `?retention` sub-resource.

<Columns cols={2}>
  <Card title="GOVERNANCE" icon="shield">
    Retention can be shortened or a locked object deleted, but only by a caller
    who sends `X-Amz-Bypass-Governance-Retention: true` **and** holds
    `storage:BypassGovernanceRetention` on the object.
  </Card>

  <Card title="COMPLIANCE" icon="lock">
    Nothing bypasses it. A `?retention` write that would move the date earlier
    is refused, so retention can only ever be extended.
  </Card>
</Columns>

A legal hold (`?legal-hold`) is independent of the retention date: while it is
on, the object cannot be deleted regardless of when retention expires. A delete
blocked by either shows up as `403`, not a silent no-op.

In the console both live on the object itself: its **Properties** tab has a
**Retention** card (**Mode**, **Retain until**) and a **Legal hold** switch,
and **Save changes** applies them together. Shortening a `GOVERNANCE` date
offers the bypass as a switch rather than making you send the header. The
upload page has no object-lock fields, though, so setting retention **at
upload** is API only.

## Encryption

Server-side encryption is **opt-in**, and it is off until you turn it on. Two
ways:

<Tabs>
  <Tab title="Console">
    The **Default encryption** card on the bucket's **Settings** tab is a
    switch and a **Save**. It sets the bucket default and nothing else — the
    console's upload page has no encryption field, so encrypting a single
    object against a bucket with no default is API only.
  </Tab>

  <Tab title="API">
    Per bucket, which is the one to reach for:

    ```bash theme={null}
    PUT /v1/buckets/{bucket}/encryption
    { "encryption": { "rules": [ { "default": { "sse_algorithm": "AES256" } } ] } }
    ```

    Or per object, on the write itself:

    ```bash theme={null}
    PUT /v1/buckets/{bucket}/objects/{key}
    X-Amz-Server-Side-Encryption: AES256
    ```
  </Tab>
</Tabs>

The bucket default applies to writes that carry no header of their own — a
per-request header wins over it. Only `AES256` is supported; `aws:kms` and
customer-provided keys are rejected with a clear error rather than being
silently downgraded.

<Info>
  A multipart upload carries no encryption header of its own on each part: the
  decision is made when the upload is created and survives to the assembled
  object. Check `ServerSideEncryption` on a `HEAD` of the finished object, not
  on the create response.
</Info>

## Lifecycle rules

`PUT /v1/buckets/{bucket}/lifecycle` replaces the whole configuration. Each rule
needs a `status` and at least one action; a rule with `status: disabled` stays in
the configuration but is skipped during evaluation.

<Tabs>
  <Tab title="Console">
    The bucket's **Management** tab has a **Lifecycle rules** card. **Add
    rule** gives you a **Prefix filter** and the day-based actions: **Expire
    current objects after (days)**, **Expire noncurrent versions after
    (days)** with an optional **Always keep newest (versions)**, and **Abort
    incomplete multipart uploads after (days)**. Each rule has its own enable
    switch, and **Save** writes the whole set.
  </Tab>

  <Tab title="API">
    ```json theme={null}
    {
      "lifecycle": {
        "rules": [
          {
            "id": "archive-then-expire",
            "status": "enabled",
            "filter": { "prefix": "logs/" },
            "transition": { "days": 30, "storage_class": "COLD" },
            "expiration": { "days": 365 },
            "abort_incomplete_multipart_upload": { "days_after_initiation": 7 }
          }
        ]
      }
    }
    ```
  </Tab>
</Tabs>

<Warning>
  **A `transition` can only be set through the API, and the console will drop
  one.** The **Lifecycle rules** card has no field for a storage-class
  transition, and a save from that card replaces the whole configuration with
  what the card models — so saving it on a bucket whose rules transition to
  `COLD` silently removes the transition. Manage lifecycle through the API on
  any bucket that uses one.

  An absolute `expiration.date` is API-only too, but that one the card does
  preserve: it round-trips a date it cannot edit, and only replaces it if you
  type a number of days.
</Warning>

| Action                              | What it does                                                                                                         |
| ----------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `transition`                        | Moves matching objects to another storage class once they are old enough.                                            |
| `expiration`                        | Deletes objects past `days` (from last-modified) or after `date`.                                                    |
| `noncurrent_version_expiration`     | Removes non-current versions past `noncurrent_days`, optionally keeping the `newer_noncurrent_versions` most recent. |
| `abort_incomplete_multipart_upload` | Discards uploads still unfinished `days_after_initiation` later.                                                     |

Rules are evaluated independently, so an object matching several is subject to
all their actions. `filter.prefix` narrows a rule; an absent or empty filter
applies it to the whole bucket.

<Note>
  A transition keeps the object's identity — same key, same version id, same
  last-modified — and moves only its bytes. That is what makes pairing a
  transition with an expiration work: the move does not restart the expiry
  clock.
</Note>

Two constraints on transitions, both rejected at write time rather than failing
silently later:

* **A transition must come strictly before the rule's expiration.** Otherwise
  the object would be deleted before it ever moved.
* **One transition per rule.** With two storage classes a second has nowhere to
  go. The S3 endpoint accepts a single-element `<Transition>` list and rejects
  longer ones rather than applying the first and ignoring the rest.

## Quotas

Two counters, both scoped to your organization within a region:

| Quota               | Counts               | Refused with    |
| ------------------- | -------------------- | --------------- |
| `buckets`           | Bucket count.        | `409` on create |
| `object_storage_gb` | Stored object bytes. | `403` on write  |

`object_storage_gb` is enforced by comparing what you actually store against the
limit, not by a running counter. That means space freed by a lifecycle
expiration, a version delete or a bucket purge comes back on its own — there is
no counter left holding a phantom charge against your cap.

## Access control

Two independent layers decide every object request:

<Columns cols={2}>
  <Card title="IAM policies" icon="shield">
    What your own principals may do, written against
    `crn:storage:<region>:<account>:bucket/<name>` for the bucket and
    `crn:storage:<region>:<account>:bucket/<name>/<key>` for an object. See
    [writing policies](/iam/policies).
  </Card>

  <Card title="The bucket policy" icon="file-lock">
    A document attached to the bucket itself, at
    `PUT /v1/buckets/{bucket}/policy`. This is what grants access to principals
    outside your account — including anonymous readers.
  </Card>
</Columns>

An **anonymous** request is evaluated against the bucket policy alone: no policy,
no access. For an **authenticated** caller, an explicit `deny` in either layer
wins outright; otherwise an allow from either layer is enough to proceed.

<Warning>
  A bucket policy that allows anonymous `storage:GetObject` makes those objects
  public to the internet. There is no second switch guarding it — the policy is
  the switch.
</Warning>

Object tags are policy context, so access can be fenced on the object itself:
`s3:ExistingObjectTag/<key>` matches what is already on the object,
`s3:RequestObjectTag/<key>` what a write is trying to set, and
`basalt:TagKeys` is the set of keys a request carries.

Both layers stop applying the moment the bucket owner's organization is
suspended — cross-account grantees and anonymous readers of a public bucket
included.

## Deleting a bucket

`DELETE /v1/buckets/{bucket}` does two quite different things depending on
whether deletion protection is on:

<Tabs>
  <Tab title="Protection off (default)">
    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.
  </Tab>

  <Tab title="Protection on">
    The delete is **scheduled** for the end of the recovery window rather than
    performed, and it is accepted whether or not the bucket is empty.
    `scheduled_deletion_at` appears on the bucket, and
    `POST /v1/buckets/{bucket}/restore` cancels it any time before that
    deadline — in the console, the scheduled bucket carries a **Cancel
    deletion** action that does the same thing. Calling delete again while a
    deletion is already scheduled is a no-op, not a second window.

    <Warning>
      When the deadline passes, the bucket is emptied and purged — **its
      objects go with it**. Protection buys you a window to change your mind,
      not a refusal to delete a bucket with data in it.
    </Warning>
  </Tab>
</Tabs>

<Tabs>
  <Tab title="Console">
    The **Deletion protection** card on the bucket's **Settings** tab is a
    switch; turning it on reveals **Recovery window (days)**, and **Save**
    applies both.
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    PUT /v1/buckets/{bucket}/deletion-protection
    { "enabled": true, "recovery_days": 14 }
    ```
  </Tab>
</Tabs>

`recovery_days` is clamped to **1–30**; `0` uses the default of **7 days**.

## Working with objects through the storage API

You do not need an S3 client. The storage API exposes the same object plane
under `/v1/buckets/{bucket}/objects`, signed the Basaltic way like every other
API call — useful for a service that already holds Basaltic credentials and
should not carry an S3 SDK:

<Tabs>
  <Tab title="Console">
    A bucket's **Objects** tab is a file browser over the same routes.
    **Upload** opens the **Upload objects** page, where you set a **Folder
    prefix**, pick a **Storage class** — **Standard** or **Cold** — and add
    **Files**. **New folder** creates a prefix, and selecting rows gives you
    **Delete selected**, which issues one delete per key rather than a bulk
    request — see the note below.

    Opening an object gives you **Download** and **Delete**, a **Versions**
    tab, and a **Properties** tab carrying its **Tags** alongside the
    [retention controls](#object-lock-and-retention).
  </Tab>

  <Tab title="API">
    ```bash theme={null}
    PUT    /v1/buckets/{bucket}/objects/{key}     # body is the object bytes
    GET    /v1/buckets/{bucket}/objects/{key}     # Range requests honoured (206)
    DELETE /v1/buckets/{bucket}/objects/{key}     # ?versionId deletes one version
    GET    /v1/buckets/{bucket}/objects?prefix=&delimiter=&marker=&max_keys=
    ```
  </Tab>
</Tabs>

Per-object sub-resources ride as query parameters on these routes — `?tagging`,
`?retention`, `?legal-hold` — and the same `X-Amz-*` headers apply on upload for
storage class, encryption and object-lock settings. `delimiter` gives you the
usual folder semantics through `common_prefixes`, and `max_keys` is capped at
1000 per page.

<Note>
  Bulk delete (`POST ?delete`) and server-side copy (`x-amz-copy-source`) are S3
  endpoint features. Through the storage API, delete objects one at a time.
</Note>

## Troubleshooting

<AccordionGroup>
  <Accordion title="SignatureDoesNotMatch" icon="key-round">
    The secret does not match the access key, or something the client signed
    was rewritten in transit. Check first that the credential is right, then
    that your client is signing with SigV4 (`signature_version="s3v4"`) — the
    endpoint accepts nothing older.

    A key or query value containing a space or a `+` is a classic case: those
    have to be percent-encoded in the canonical request, and an SDK doing it
    correctly will interoperate.
  </Accordion>

  <Accordion title="RequestTimeTooSkewed" icon="clock">
    Your clock is more than 15 minutes from the server's. Fix time sync on the
    machine making the request; there is no way to widen the window.
  </Accordion>

  <Accordion title="AccessDenied on a bucket you own" icon="shield">
    An explicit `deny` in **either** layer wins, so start there: a bucket
    policy denying something is enough to block a caller their IAM policies
    allow, and the reverse holds too. With no explicit deny anywhere, you need
    an allow from one of the two — a bucket policy that names other principals
    is not itself a denial, but it will not stand in for the IAM grant you are
    missing.

    Deleting an object also fails with `403` when it is protected by an active
    retention period or a legal hold, which reads like a permission problem but
    is not one.
  </Accordion>

  <Accordion title="BucketNotEmpty on delete" icon="triangle-alert">
    Objects, versions, or in-flight multipart uploads remain. List uploads with
    `GET /v1/buckets/{bucket}/multipart-uploads` — an abandoned upload holds
    the bucket open just as an object does, and does not show in an object
    listing.
  </Accordion>

  <Accordion title="EntityTooSmall at completion" icon="layers">
    A part other than the last one is under 5 MiB. The floor is checked when
    the upload completes, so this surfaces after every part has been staged.
    Re-upload with larger parts.
  </Accordion>

  <Accordion title="The object is not encrypted despite the bucket default" icon="lock">
    A bucket default applies to writes made **after** it was set; existing
    objects are untouched. Re-upload anything already stored if you need it
    covered. Check `ServerSideEncryption` on a `HEAD` of the stored object,
    which is the only answer that reflects what was written.
  </Accordion>
</AccordionGroup>

## S3 error codes

The S3 endpoint answers in S3's own XML vocabulary, so an SDK's error handling
works unchanged:

| Code                                | Status | Meaning                                                                    |
| ----------------------------------- | ------ | -------------------------------------------------------------------------- |
| `AccessDenied`                      | 403    | Neither layer allowed the request.                                         |
| `SignatureDoesNotMatch`             | 403    | Signature did not verify.                                                  |
| `RequestTimeTooSkewed`              | 403    | More than 15 minutes of clock drift.                                       |
| `NoSuchBucket` / `NoSuchKey`        | 404    | Not found.                                                                 |
| `NoSuchUpload`                      | 404    | Unknown or already-aborted multipart upload.                               |
| `BucketAlreadyExists`               | 409    | Name taken by another account.                                             |
| `BucketNotEmpty`                    | 409    | Objects or uploads remain.                                                 |
| `InvalidBucketName`                 | 400    | Name broke one of the naming rules.                                        |
| `TooManyBuckets`                    | 400    | Your organization's bucket quota is exhausted.                             |
| `EntityTooLarge` / `EntityTooSmall` | 400    | Past the single-upload ceiling, or a short non-final part.                 |
| `InvalidPart` / `InvalidPartOrder`  | 400    | A named part is missing, its ETag mismatched, or the list is out of order. |
| `NotImplemented`                    | 501    | An S3 operation this endpoint does not route.                              |

## Next

<CardGroup cols={2}>
  <Card title="Block storage" icon="hard-drive" href="/storage">
    Volumes, snapshots and snapshot policies.
  </Card>

  <Card title="Authentication" icon="key-round" href="/authentication">
    Access keys, temporary credentials and session tokens.
  </Card>

  <Card title="Writing policies" icon="shield" href="/iam/policies">
    IAM statements, bucket policies and tag conditions.
  </Card>

  <Card title="API reference" icon="code" href="/api-reference/introduction">
    Every bucket operation, with request and response schemas.
  </Card>
</CardGroup>
