# HeadBucket

HeadBucket is the lightest way to ask "is this bucket here, and may I use it?". A bucket that
exists in the endpoint's region and that your access key can reach answers `200 OK` with no body,
and nothing else is read or changed. Most S3 clients and tools make this call before they start
working with a bucket.

**Info: Good to know on STRG**

- Each endpoint answers for the buckets in its own region. A bucket in another region answers
  `404 Not Found` rather than a redirect, so the bucket's own regional endpoint is the one to ask.
- `x-amz-bucket-region` comes with every `200`, and it is always the endpoint's region - the
  region the bucket was just found in.
- A bucket name held by someone else answers `404 Not Found`, exactly like a name nobody holds,
  so the answer never says whether another customer has it.
- HeadBucket is always a signed request. Anonymous visitors to a public bucket read its objects
  with [GetObject](https://docs.strg.com/s3-api/GetObject) and [HeadObject](https://docs.strg.com/s3-api/HeadObject) instead.
- `x-amz-expected-bucket-owner` is welcome and has nothing to check: a key only ever reaches
  buckets of its own account.

## When you'd use it

- **S3 clients and SDK tools.** Sync and backup tools commonly check that their target bucket is
  there before they upload anything, and HeadBucket gives them a quick, cheap answer.
- **Create-if-missing scripts.** A deploy script or an agent setting up its storage can ask
  whether the bucket already exists, and call [CreateBucket](https://docs.strg.com/s3-api/CreateBucket) only on a `404`.
- **Checking a key.** A health check or a newly configured application can confirm that its
  access key reaches the bucket it was given: `200` means it does, `403` that the key is not
  allowed to.
- **Finding where a bucket lives.** A script handed only a bucket name can ask each regional
  endpoint in turn; the one that answers `200` is the bucket's region.

## Request

```http
HEAD / HTTP/1.1
Host: my-bucket.s3.eu-central-1.strg.com
Authorization: AWS4-HMAC-SHA256 Credential=...
x-amz-date: 20260926T120000Z
x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
```

The path-style form is `HEAD /my-bucket` on `s3.eu-central-1.strg.com` -
[Addressing](https://docs.strg.com/s3-api/overview#addressing) explains both.

### Permissions

The access key needs **List buckets** (`buckets:list`), which every access key holds, whether it
covers all buckets or only some. A key limited to certain buckets can check those buckets, and any
other bucket answers `403 Forbidden`. [Access keys](https://docs.strg.com/access-keys#always-included) has the
details.

### URI parameters

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `Bucket` | String | Yes | The bucket to check. |

HeadBucket takes no query parameters. Apart from a presigned URL's `X-Amz-*` parameters and the
JavaScript SDK's `x-id`, a query parameter answers `501 Not Implemented`, so a client knows
straight away.

### Request headers

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `x-amz-expected-bucket-owner` | String | No | Welcome, with nothing to check: an access key only ever reaches its own account's buckets. |

### Request body

None.

## Response

A bucket that exists in the endpoint's region and that your key can reach answers `200 OK` with
no body.

### Response headers

| Name | Description |
| --- | --- |
| `x-amz-bucket-region` | The bucket's region - always the region of the endpoint you called, such as `eu-central-1`. Sent with `200 OK`. |
| `x-amz-request-id` | The request ID, handy for a support request. |
| `x-amz-id-2` | The same request ID. |
| `x-amz-bucket-arn` | Not part of the answer. |
| `x-amz-bucket-location-type` | Not part of the answer. |
| `x-amz-bucket-location-name` | Not part of the answer. |
| `x-amz-access-point-alias` | Not part of the answer. |

### Response body

None.

## Errors

A `HEAD` response never has a body, so the error code in the table does not reach your client -
the HTTP status does. The AWS CLI and boto3 report the status itself (`404`, `403`) as the error
code; the JavaScript SDK reports `NotFound` or `Forbidden` for those two, with the status in
`$metadata.httpStatusCode`. The table lists the situation behind each status.

| Code | HTTP status | When |
| --- | --- | --- |
| `NoSuchBucket` | 404 | The bucket does not exist, belongs to someone else, or is in a region other than the endpoint's - including when your account has nothing stored in this endpoint's region yet. |
| `AccessDenied` | 403 | The access key is limited to buckets that do not include this one, your account is not active, the request is unsigned, or a presigned URL has expired. |
| `InvalidAccessKeyId` | 403 | The access key does not exist, has expired or been revoked, or was created so recently that it is not yet usable in this endpoint's region: you already store data in this region, and the key is still being set up there. The **Access Keys** page shows **Setting up in** *region* until it is **Ready in** *region*, usually within a few minutes - [When a key starts working](https://docs.strg.com/access-keys#when-a-key-starts-working) has the details. |
| `SignatureDoesNotMatch` | 403 | The request signature does not match. |
| `RequestTimeTooSkewed` | 403 | The request time is more than 15 minutes from the server's. |
| `AuthorizationHeaderMalformed` | 400 | The signature's credential scope names a region other than the endpoint's. |
| `InvalidBucketName` | 400 | The bucket name is not a valid STRG bucket name - names are 3-58 characters. |
| `NotImplemented` | 501 | The request carries a query parameter this operation does not take. |
| `SlowDown` | 503 | Too many requests at once; a retry after the `Retry-After` interval goes through. |
| `ServiceUnavailable` | 503 | A temporary failure; a retry usually succeeds. |

## Differences from Amazon S3

| Aspect | Amazon S3 | STRG |
| --- | --- | --- |
| Bucket in another region | Answered from any region, with `x-amz-bucket-region` naming the bucket's region | `404 Not Found`, with no `x-amz-bucket-region`. The bucket's own regional endpoint answers it; the SDKs' region-redirect retry does not come into play |
| `x-amz-bucket-region` | Sent on success and on most errors | Sent with `200 OK`, and always the endpoint's region |
| Bucket owned by another account | `403 Forbidden` | `404 Not Found`, the same as a name nobody holds |
| Unsigned requests to public buckets | Accepted where the bucket allows public listing | Always signed; an unsigned request answers `403 Forbidden`. Public buckets are read anonymously through [GetObject](https://docs.strg.com/s3-api/GetObject) and [HeadObject](https://docs.strg.com/s3-api/HeadObject) - [Anonymous reads](https://docs.strg.com/s3-api/overview#anonymous-reads) |
| `x-amz-expected-bucket-owner` | Checked against the bucket owner | Welcome, with nothing to check - a key only reaches its own account's buckets |
| `x-amz-bucket-arn`, `x-amz-bucket-location-type`, `x-amz-bucket-location-name`, `x-amz-access-point-alias` | Returned where they apply | Not part of the answer |

## Examples

**AWS CLI**

```bash
aws s3api head-bucket \
  --bucket my-bucket \
  --endpoint-url https://s3.eu-central-1.strg.com \
  --region eu-central-1
```

```json
{
    "BucketRegion": "eu-central-1"
}
```

When the bucket is not there, the command exits non-zero with
`An error occurred (404) when calling the HeadBucket operation: Not Found`.

**Python (boto3)**

```python
import boto3
from botocore.exceptions import ClientError

s3 = boto3.client(
    "s3",
    endpoint_url="https://s3.eu-central-1.strg.com",
    region_name="eu-central-1",
)

try:
    response = s3.head_bucket(Bucket="my-bucket")
    print(f"my-bucket exists in {response['BucketRegion']}")
except ClientError as error:
    if error.response["Error"]["Code"] == "404":
        print("my-bucket is not in eu-central-1")
    else:
        raise
```

**JavaScript (AWS SDK v3)**

```javascript
import { HeadBucketCommand, S3Client } from "@aws-sdk/client-s3";

const client = new S3Client({
  endpoint: "https://s3.eu-central-1.strg.com",
  region: "eu-central-1",
});

try {
  const response = await client.send(new HeadBucketCommand({ Bucket: "my-bucket" }));
  console.log(`my-bucket exists in ${response.BucketRegion}`);
} catch (error) {
  if (error.$metadata?.httpStatusCode === 404) {
    console.log("my-bucket is not in eu-central-1");
  } else {
    throw error;
  }
}
```

Finding which region a bucket is in works the same way with HeadBucket as with
[GetBucketLocation](https://docs.strg.com/s3-api/GetBucketLocation#finding-a-buckets-region): each regional endpoint is
asked in turn, and the one answering `200` is the bucket's. STRG has two regions, `us-east-1`
and `eu-central-1`, listed with their endpoints in
[Regions and endpoints](https://docs.strg.com/s3-api/overview#regions-and-endpoints), so it takes at most two requests.
An endpoint for a region where your account has no bucket at all answers `404` as well, which is
the truth: the bucket is not there.

## Related

- [GetBucketLocation](https://docs.strg.com/s3-api/GetBucketLocation) - the bucket's region, in an XML body
- [ListBuckets](https://docs.strg.com/s3-api/ListBuckets)
- [HeadObject](https://docs.strg.com/s3-api/HeadObject)
- [S3 API overview](https://docs.strg.com/s3-api/overview#differences-from-amazon-s3) - every difference from Amazon S3
- [Access keys](https://docs.strg.com/access-keys)
