---
title: "Object storage — API reference"
description: "Typed S3 storage shortcuts: buckets, access keys (secret shown once), usage, connection details."
url: https://www.gyrohosting.com/developers/en/api/object-storage.html
language: en
openapi: https://www.gyrohosting.com/developers/openapi.json
api_version: 2.30.0
---

# Object storage

Typed S3 storage shortcuts: buckets, access keys (secret shown once), usage, connection details.

Base URL: `https://www.gyrohosting.com/api/v1/client` · OpenAPI: https://www.gyrohosting.com/developers/openapi.json


## GET /cloud/storage/{id}/buckets

**GET buckets (GyroDepolama)** · operationId `typed_get_storage__id__buckets` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_get_storage__id__buckets.json

Typed shortcut of the generic bridge: tool `buckets`. Body fields are the tool's own fields. Each item has `s3_name` (2.20.0): the name to use in S3 clients. Equal to `name` on Object Storage; on Protected Storage it stays the original name after a rename.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |

### Responses

- **200**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro api request GET /cloud/storage/12345/buckets
```
```hcl
# resource gyro_storage_bucket: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_bucket
resource "gyro_storage_bucket" "assets" {
  service_id = 456
  name       = "assets"
}

# Protected Storage: versioned bucket with object lock (WORM).
# The real bucket name is "archive-<service code>": use full_name in S3 clients and other resources.
resource "gyro_storage_bucket" "archive" {
  service_id  = 789
  name        = "archive"
  object_lock = true
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## POST /cloud/storage/{id}/buckets

**Create a bucket** · operationId `typed_post_storage__id__buckets` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_post_storage__id__buckets.json

Creates a bucket. Object Storage: the name is private to the service. Protected Storage: every bucket is versioned and the name gets the service code suffix (`<name>-<code>`); `object_lock: true` creates a WORM bucket (needs a free locked-bucket right: Object Lock add-on, see `GET /cloud/storage/{id}/object-lock`). Object lock can only be chosen at creation and never turned off. `object_lock: true` on a standard Object Storage service answers 409 `feature_not_available`. Needs `"confirm": true`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |

### Request body

| Field | Type | Description |
|---|---|---|
| `bucket` (required) | string | Bucket name (3-58, lowercase letters, digits, -) |
| `object_lock` | boolean | Protected Storage only: create with object lock (WORM). Cannot be changed later |
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X POST 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"bucket": "string", "object_lock": true, "confirm": true}'
```
```bash
gyro api request POST /cloud/storage/12345/buckets --data '{"bucket": "string", "object_lock": true, "confirm": true}'
```
```hcl
# resource gyro_storage_bucket: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_bucket
resource "gyro_storage_bucket" "assets" {
  service_id = 456
  name       = "assets"
}

# Protected Storage: versioned bucket with object lock (WORM).
# The real bucket name is "archive-<service code>": use full_name in S3 clients and other resources.
resource "gyro_storage_bucket" "archive" {
  service_id  = 789
  name        = "archive"
  object_lock = true
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.post("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets", headers=h, json={"bucket": "string", "object_lock": True, "confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"bucket": "string", "object_lock": true, "confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## DELETE /cloud/storage/{id}/buckets/{bucket}

**DELETE buckets:delete (GyroDepolama)** · operationId `typed_delete_storage__id__buckets__bucket` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_delete_storage__id__buckets__bucket.json

Typed shortcut of the generic bridge: tool `buckets`, action `delete`. Body fields are the tool's own fields. Needs `"confirm": true`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |

### Request body

| Field | Type | Description |
|---|---|---|
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X DELETE 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"confirm": true}'
```
```bash
gyro api request DELETE /cloud/storage/12345/buckets/1 --data '{"confirm": true}'
```
```hcl
# resource gyro_storage_bucket: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_bucket
resource "gyro_storage_bucket" "assets" {
  service_id = 456
  name       = "assets"
}

# Protected Storage: versioned bucket with object lock (WORM).
# The real bucket name is "archive-<service code>": use full_name in S3 clients and other resources.
resource "gyro_storage_bucket" "archive" {
  service_id  = 789
  name        = "archive"
  object_lock = true
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.delete("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1", headers=h, json={"confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1", {
  method: "DELETE",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/{id}/keys

**GET access-keys (GyroDepolama)** · operationId `typed_get_storage__id__keys` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_get_storage__id__keys.json

Typed shortcut of the generic bridge: tool `access-keys`. Body fields are the tool's own fields.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |

### Responses

- **200**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/keys' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro api request GET /cloud/storage/12345/keys
```
```hcl
# resource gyro_storage_key: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_key
resource "gyro_storage_key" "ci" {
  service_id = 456
  label      = "ci-uploader"
}

output "s3_secret" {
  value     = gyro_storage_key.ci.secret_key
  sensitive = true
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/keys", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/keys", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## POST /cloud/storage/{id}/keys

**POST access-keys:create (GyroDepolama)** · operationId `typed_post_storage__id__keys` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_post_storage__id__keys.json

Typed shortcut of the generic bridge: tool `access-keys`, action `create`. Body fields are the tool's own fields.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |

### Request body

| Field | Type | Description |
|---|---|---|
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X POST 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/keys' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"confirm": true}'
```
```bash
gyro api request POST /cloud/storage/12345/keys --data '{"confirm": true}'
```
```hcl
# resource gyro_storage_key: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_key
resource "gyro_storage_key" "ci" {
  service_id = 456
  label      = "ci-uploader"
}

output "s3_secret" {
  value     = gyro_storage_key.ci.secret_key
  sensitive = true
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.post("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/keys", headers=h, json={"confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/keys", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## POST /cloud/storage/{id}/keys/{key}/rotate

**POST access-keys:rotate (GyroDepolama)** · operationId `typed_post_storage__id__keys__key__rotate` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_post_storage__id__keys__key__rotate.json

Typed shortcut of the generic bridge: tool `access-keys`, action `rotate`. Body fields are the tool's own fields. Needs `"confirm": true`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `key` (required) | path | string |  |

### Request body

| Field | Type | Description |
|---|---|---|
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X POST 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/keys/1/rotate' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"confirm": true}'
```
```bash
gyro storage key rotate 12345 --help    # all flags
```
```hcl
# resource gyro_storage_key: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_key
resource "gyro_storage_key" "ci" {
  service_id = 456
  label      = "ci-uploader"
}

output "s3_secret" {
  value     = gyro_storage_key.ci.secret_key
  sensitive = true
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.post("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/keys/1/rotate", headers=h, json={"confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/keys/1/rotate", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## DELETE /cloud/storage/{id}/keys/{key}

**DELETE access-keys:delete (GyroDepolama)** · operationId `typed_delete_storage__id__keys__key` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_delete_storage__id__keys__key.json

Typed shortcut of the generic bridge: tool `access-keys`, action `delete`. Body fields are the tool's own fields. Needs `"confirm": true`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `key` (required) | path | string |  |

### Request body

| Field | Type | Description |
|---|---|---|
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X DELETE 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/keys/1' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"confirm": true}'
```
```bash
gyro api request DELETE /cloud/storage/12345/keys/1 --data '{"confirm": true}'
```
```hcl
# resource gyro_storage_key: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_key
resource "gyro_storage_key" "ci" {
  service_id = 456
  label      = "ci-uploader"
}

output "s3_secret" {
  value     = gyro_storage_key.ci.secret_key
  sensitive = true
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.delete("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/keys/1", headers=h, json={"confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/keys/1", {
  method: "DELETE",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/{id}/usage

**GET usage (GyroDepolama)** · operationId `typed_get_storage__id__usage` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_get_storage__id__usage.json

Typed shortcut of the generic bridge: tool `usage`. Body fields are the tool's own fields.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |

### Responses

- **200**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/usage' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage usage get 12345
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/usage", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/usage", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## POST /cloud/storage/{id}/buckets/{bucket}/rename

**Rename a bucket** · operationId `typed_post_storage__id__buckets__bucket__rename` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_post_storage__id__buckets__bucket__rename.json

Bucket names are private to the service (other customers may use the same name). Data is not moved. S3 access by the old name stops at once; the website moves to `<new>-<service code>.web.gyro-s3.com` and the old website address redirects (301) for 40 days; share links keep working. Max 5 renames per bucket per day; the old name cannot be reused inside the service for 40 days. Needs `"confirm": true`. **Protected Storage (2.20.0):** renaming is supported: the bucket's display name and website address (`<new name>-<service code>.kweb.gyro-s3.com`, old address 301 for 40 days) change; the **S3 name stays the same** (versions and locks are never moved), so S3 tools keep using it. The answer carries `s3_name`; `GET /cloud/storage/{id}/buckets` lists `s3_name` for every bucket.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |

### Request body

| Field | Type | Description |
|---|---|---|
| `new_name` (required) | string |  |
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X POST 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/rename' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"new_name": "string", "confirm": true}'
```
```bash
gyro storage bucket rename 12345 --help    # all flags
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.post("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/rename", headers=h, json={"new_name": "string", "confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/rename", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"new_name": "string", "confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/{id}/buckets/{bucket}/objects

**List objects in a bucket folder** · operationId `typed_get_storage__id__buckets__bucket__objects` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_get_storage__id__buckets__bucket__objects.json

One page (200) of a folder: `folders` (common prefixes) and `files` (key, size, modified). Pass `next` from the previous page to continue.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |
| `prefix` | query | string | Folder, ends with / (empty = root) |
| `search` | query | string | Name prefix inside the folder |
| `next` | query | string | Continuation token |

### Responses

- **200**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage object list 12345
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## POST /cloud/storage/{id}/buckets/{bucket}/objects/upload-url

**Presigned upload URLs** · operationId `typed_post_storage__id__buckets__bucket__objects_upload_url` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_post_storage__id__buckets__bucket__objects_upload_url.json

Up to 100 files per call, max 5 GiB each. PUT the file body to each `url` within 15 minutes; data never passes through the API. Larger files: any S3 client (multipart).


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |

### Request body

| Field | Type | Description |
|---|---|---|
| `prefix` | string | Target folder (ends with /) |
| `items` (required) | array<object> |  |
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X POST 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/upload-url' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"prefix": "string", "items": [{"name": "string", "size": 1}], "confirm": true}'
```
```bash
gyro storage object upload-url 12345 --help    # all flags
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.post("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/upload-url", headers=h, json={"prefix": "string", "items": [{"name": "string", "size": 1}], "confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/upload-url", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"prefix": "string", "items": [{"name": "string", "size": 1}], "confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## POST /cloud/storage/{id}/buckets/{bucket}/objects/download-url

**Presigned download URL** · operationId `typed_post_storage__id__buckets__bucket__objects_download_url` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_post_storage__id__buckets__bucket__objects_download_url.json

Returns a 15-minute GET URL (Content-Disposition: attachment). Downloads count toward the service's monthly traffic.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |

### Request body

| Field | Type | Description |
|---|---|---|
| `key` (required) | string | Object key |
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X POST 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/download-url' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"key": "string", "confirm": true}'
```
```bash
gyro storage object download-url 12345 --help    # all flags
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.post("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/download-url", headers=h, json={"key": "string", "confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/download-url", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"key": "string", "confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## POST /cloud/storage/{id}/buckets/{bucket}/objects/delete

**Delete objects** · operationId `typed_post_storage__id__buckets__bucket__objects_delete` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_post_storage__id__buckets__bucket__objects_delete.json

Deletes up to 1000 keys in one call. Needs `"confirm": true`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |

### Request body

| Field | Type | Description |
|---|---|---|
| `keys` (required) | array<string> |  |
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X POST 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/delete' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"keys": ["string"], "confirm": true}'
```
```bash
gyro storage object delete 12345 --help    # all flags
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.post("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/delete", headers=h, json={"keys": ["string"], "confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/delete", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"keys": ["string"], "confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## POST /cloud/storage/{id}/buckets/{bucket}/folders

**Create a folder** · operationId `typed_post_storage__id__buckets__bucket__folders` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_post_storage__id__buckets__bucket__folders.json

Creates an empty folder marker `prefix + name + /`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |

### Request body

| Field | Type | Description |
|---|---|---|
| `prefix` | string |  |
| `name` (required) | string |  |
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X POST 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/folders' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"prefix": "string", "name": "string", "confirm": true}'
```
```bash
gyro storage folder create 12345 --help    # all flags
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.post("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/folders", headers=h, json={"prefix": "string", "name": "string", "confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/folders", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"prefix": "string", "name": "string", "confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## DELETE /cloud/storage/{id}/buckets/{bucket}/folders

**Delete a folder with its contents** · operationId `typed_delete_storage__id__buckets__bucket__folders` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_delete_storage__id__buckets__bucket__folders.json

Deletes every object under `prefix` (query). Large folders are deleted in batches: repeat while `more` is true. Needs `"confirm": true`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |
| `prefix` | query | string | Folder, ends with / |

### Request body

| Field | Type | Description |
|---|---|---|
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X DELETE 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/folders' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"confirm": true}'
```
```bash
gyro storage folder delete 12345 --help    # all flags
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.delete("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/folders", headers=h, json={"confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/folders", {
  method: "DELETE",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/{id}/buckets/{bucket}/folders/count

**Count objects in a folder** · operationId `typed_get_storage__id__buckets__bucket__folders_count` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_get_storage__id__buckets__bucket__folders_count.json

Number of objects under `prefix` (`more` = over 100000 or time limit).


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |
| `prefix` | query | string | Folder, ends with / |

### Responses

- **200**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/folders/count' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage folder count 12345
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/folders/count", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/folders/count", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/{id}/connect

**GET connect (GyroDepolama)** · operationId `typed_get_storage__id__connect` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_get_storage__id__connect.json

Typed shortcut of the generic bridge: tool `connect`. Body fields are the tool's own fields.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |

### Responses

- **200**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/connect' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage connect get 12345
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/connect", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/connect", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/{id}/buckets/{bucket}/website

**Static website settings of a bucket** · operationId `typed_get_storage__id__buckets__bucket__website` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_get_storage__id__buckets__bucket__website.json

`enabled`, `url`, `index_document` (entry file, default index.html), `error_document` (404 page) and `spa` (single-page app fallback).


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |

### Responses

- **200**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/website' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage website get 12345
```
```hcl
# resource gyro_storage_bucket_website: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_bucket_website
# Single-page app (React / Vue / Angular) published from a bucket
resource "gyro_storage_bucket_website" "app" {
  service_id     = 456 # object storage service
  bucket         = gyro_storage_bucket.assets.name
  index_document = "index.html" # entry file (default index.html)
  spa            = true         # missing paths (/dashboard/settings, reloads) return index.html with 200
}

# Classic static site with its own 404 page
resource "gyro_storage_bucket_website" "docs" {
  service_id     = 456
  bucket         = "docs"
  error_document = "404.html"
}

output "app_url" { value = gyro_storage_bucket_website.app.url }
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/website", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/website", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## PUT /cloud/storage/{id}/buckets/{bucket}/website

**Enable / update the static website** · operationId `typed_put_storage__id__buckets__bucket__website` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_put_storage__id__buckets__bucket__website.json

Full replace: omitted fields fall back to the defaults (index.html, no error page, SPA off). `index_document` is a file name (no `/`), served at the root and in every folder. `spa: true` answers every missing path with the entry file and status 200 (React/Vue/Angular routing, page reloads); real files are served as they are and the error page is not used while SPA is on. Enabling a disabled website needs `accept: true` (abuse rules: everything in the bucket becomes public). Address: `https://<bucket>-<service code>.web.gyro-s3.com` and connected custom domains.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |

### Request body

| Field | Type | Description |
|---|---|---|
| `index_document` | string | Entry file name, default index.html (e.g. app.html) |
| `error_document` | string | Object key of the 404 page (e.g. 404.html or errors/404.html), empty = none |
| `spa` | boolean | Route missing sub-paths to the entry file with status 200 |
| `accept` | boolean | Accept the abuse rules (needed when the website is off) |
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X PUT 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/website' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"index_document": "string", "error_document": "string", "spa": true, "accept": true, "confirm": true}'
```
```bash
gyro storage website set 12345 --help    # all flags
```
```hcl
# resource gyro_storage_bucket_website: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_bucket_website
# Single-page app (React / Vue / Angular) published from a bucket
resource "gyro_storage_bucket_website" "app" {
  service_id     = 456 # object storage service
  bucket         = gyro_storage_bucket.assets.name
  index_document = "index.html" # entry file (default index.html)
  spa            = true         # missing paths (/dashboard/settings, reloads) return index.html with 200
}

# Classic static site with its own 404 page
resource "gyro_storage_bucket_website" "docs" {
  service_id     = 456
  bucket         = "docs"
  error_document = "404.html"
}

output "app_url" { value = gyro_storage_bucket_website.app.url }
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.put("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/website", headers=h, json={"index_document": "string", "error_document": "string", "spa": True, "accept": True, "confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/website", {
  method: "PUT",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"index_document": "string", "error_document": "string", "spa": true, "accept": true, "confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## DELETE /cloud/storage/{id}/buckets/{bucket}/website

**Disable the static website** · operationId `typed_delete_storage__id__buckets__bucket__website` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_delete_storage__id__buckets__bucket__website.json

The bucket stays; settings (entry file, error page, SPA) are kept for the next enable. Needs `"confirm": true`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |

### Request body

| Field | Type | Description |
|---|---|---|
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X DELETE 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/website' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"confirm": true}'
```
```bash
gyro storage website disable 12345 --help    # all flags
```
```hcl
# resource gyro_storage_bucket_website: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_bucket_website
# Single-page app (React / Vue / Angular) published from a bucket
resource "gyro_storage_bucket_website" "app" {
  service_id     = 456 # object storage service
  bucket         = gyro_storage_bucket.assets.name
  index_document = "index.html" # entry file (default index.html)
  spa            = true         # missing paths (/dashboard/settings, reloads) return index.html with 200
}

# Classic static site with its own 404 page
resource "gyro_storage_bucket_website" "docs" {
  service_id     = 456
  bucket         = "docs"
  error_document = "404.html"
}

output "app_url" { value = gyro_storage_bucket_website.app.url }
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.delete("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/website", headers=h, json={"confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/website", {
  method: "DELETE",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/{id}/buckets/{bucket}/cors

**CORS rules of a bucket** · operationId `typed_get_storage__id__buckets__bucket__cors` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_get_storage__id__buckets__bucket__cors.json

`rules` (snake_case) and `json` (the same rules in AWS S3 format, pretty printed).


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |

### Responses

- **200**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/cors' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage cors get 12345
```
```hcl
# resource gyro_storage_bucket_cors: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_bucket_cors
resource "gyro_storage_bucket_cors" "assets" {
  service_id = 456
  bucket     = gyro_storage_bucket.assets.name
  rules = [
    {
      id              = "browser-upload" # presigned PUT from your web app
      allowed_origins = ["https://www.example.com", "https://example.com"]
      allowed_methods = ["GET", "HEAD", "PUT", "POST"]
      allowed_headers = ["*"]
      expose_headers  = ["ETag"]
      max_age_seconds = 3600
    },
    {
      allowed_origins = ["*"] # public read for fonts / images
      allowed_methods = ["GET", "HEAD"]
    },
  ]
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/cors", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/cors", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## PUT /cloud/storage/{id}/buckets/{bucket}/cors

**Replace the CORS rules of a bucket** · operationId `typed_put_storage__id__buckets__bucket__cors` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_put_storage__id__buckets__bucket__cors.json

Replaces every rule. Send `rules` (array), `CORSRules` (an AWS `put-bucket-cors` file works as is: `gyro storage cors set ID BUCKET --data @cors.json`) or `cors_json` (the JSON as text). Field names may be AWS style (`AllowedOrigins`) or snake_case (`allowed_origins`). Limits: 100 rules, 50 origins/headers per rule, 16 KB, methods GET/PUT/POST/DELETE/HEAD, `MaxAgeSeconds` 0-86400, at most one `*` per origin. An empty list removes the rules. Rules apply to the S3 endpoint and to the static website (and custom domains).


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |

### Request body

| Field | Type | Description |
|---|---|---|
| `rules` | object | CORS rules as a JSON array |
| `cors_json` | string | Alternative: the rules as JSON text ({"CORSRules":[...]} or [...]) |
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X PUT 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/cors' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"rules": {}, "cors_json": "string", "confirm": true}'
```
```bash
gyro storage cors set 12345 --help    # all flags
```
```hcl
# resource gyro_storage_bucket_cors: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_bucket_cors
resource "gyro_storage_bucket_cors" "assets" {
  service_id = 456
  bucket     = gyro_storage_bucket.assets.name
  rules = [
    {
      id              = "browser-upload" # presigned PUT from your web app
      allowed_origins = ["https://www.example.com", "https://example.com"]
      allowed_methods = ["GET", "HEAD", "PUT", "POST"]
      allowed_headers = ["*"]
      expose_headers  = ["ETag"]
      max_age_seconds = 3600
    },
    {
      allowed_origins = ["*"] # public read for fonts / images
      allowed_methods = ["GET", "HEAD"]
    },
  ]
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.put("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/cors", headers=h, json={"rules": {}, "cors_json": "string", "confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/cors", {
  method: "PUT",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"rules": {}, "cors_json": "string", "confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## DELETE /cloud/storage/{id}/buckets/{bucket}/cors

**Remove every CORS rule of a bucket** · operationId `typed_delete_storage__id__buckets__bucket__cors` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/typed_delete_storage__id__buckets__bucket__cors.json

Browsers can no longer read the bucket cross-origin. Needs `"confirm": true`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id |
| `bucket` (required) | path | string |  |

### Request body

| Field | Type | Description |
|---|---|---|
| `confirm` | boolean |  |

### Responses

- **202**: Tool data / result
- **404**: Not found — also returned for objects owned by someone else
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X DELETE 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/cors' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"confirm": true}'
```
```bash
gyro storage cors delete 12345 --help    # all flags
```
```hcl
# resource gyro_storage_bucket_cors: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_bucket_cors
resource "gyro_storage_bucket_cors" "assets" {
  service_id = 456
  bucket     = gyro_storage_bucket.assets.name
  rules = [
    {
      id              = "browser-upload" # presigned PUT from your web app
      allowed_origins = ["https://www.example.com", "https://example.com"]
      allowed_methods = ["GET", "HEAD", "PUT", "POST"]
      allowed_headers = ["*"]
      expose_headers  = ["ETag"]
      max_age_seconds = 3600
    },
    {
      allowed_origins = ["*"] # public read for fonts / images
      allowed_methods = ["GET", "HEAD"]
    },
  ]
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.delete("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/cors", headers=h, json={"confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/cors", {
  method: "DELETE",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/{id}/object-lock

**Protected Storage: object lock summary** · operationId `getStorageObjectLock` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/getStorageObjectLock.json

Locked-bucket rights (Object Lock add-on) and use, the default retention of every bucket, and the last lock scan (number of COMPLIANCE / GOVERNANCE / legal hold versions and the latest end date). Protected Storage only: a standard Object Storage service answers 409 `feature_not_available`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id (Protected Storage) |

### Responses

- **200**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: feature_not_available (standard Object Storage service) or not_actionable
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/object-lock' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage object-lock status 12345
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/object-lock", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/object-lock", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## POST /cloud/storage/{id}/object-lock/scan

**Scan locked versions now** · operationId `scanStorageObjectLock` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/scanStorageObjectLock.json

Counts locked versions in every locked bucket (up to 25 s / 5000 versions; `complete: false` when cut short).


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id (Protected Storage) |

### Responses

- **202**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: feature_not_available (standard Object Storage service) or not_actionable
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X POST 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/object-lock/scan' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage object-lock scan 12345
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.post("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/object-lock/scan", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/object-lock/scan", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/{id}/buckets/{bucket}/versions

**List object versions** · operationId `listStorageVersions` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/listStorageVersions.json

Versions and delete markers of one folder (50 per page, `/` delimiter; sub-folders in `folders`). Continue with `key_marker` + `version_id_marker` from the previous page while `truncated` is true. In locked buckets each version carries `lock_mode`, `retain_until` and `legal_hold`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id (Protected Storage) |
| `bucket` (required) | path | string | Bucket name (full name, with the -code suffix) |
| `prefix` | query | string | Folder or key prefix (empty = root) |
| `key_marker` | query | string | Continuation: next_key_marker of the previous page |
| `version_id_marker` | query | string | Continuation: next_version_id_marker of the previous page |

### Responses

- **200**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: feature_not_available (standard Object Storage service) or not_actionable
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/versions' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage versions list 12345
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/versions", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/versions", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## DELETE /cloud/storage/{id}/buckets/{bucket}/versions

**Permanently delete a version** · operationId `deleteStorageVersion` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/deleteStorageVersion.json

Permanently deletes one version or delete marker (cannot be undone). Versions under GOVERNANCE / COMPLIANCE retention or legal hold are refused with the end date. Needs `confirm=true`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id (Protected Storage) |
| `bucket` (required) | path | string | Bucket name (full name, with the -code suffix) |
| `key` (required) | query | string | Object key |
| `version_id` (required) | query | string | Version id |
| `confirm` (required) | query | boolean |  |

### Responses

- **202**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: feature_not_available (standard Object Storage service) or not_actionable
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X DELETE 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/versions' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage versions delete 12345
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.delete("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/versions", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/versions", {
  method: "DELETE",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## POST /cloud/storage/{id}/buckets/{bucket}/versions/restore

**Restore an older version** · operationId `restoreStorageVersion` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/restoreStorageVersion.json

Copies the version as the new current version (the old one stays). Max 5 GB; delete markers cannot be restored (use undelete).


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id (Protected Storage) |
| `bucket` (required) | path | string | Bucket name (full name, with the -code suffix) |

### Request body

| Field | Type | Description |
|---|---|---|
| `key` (required) | string | Object key |
| `version_id` (required) | string | Version id (from versions list) |

### Responses

- **202**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: feature_not_available (standard Object Storage service) or not_actionable
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X POST 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/versions/restore' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"key": "string", "version_id": "string"}'
```
```bash
gyro storage versions restore 12345 --help    # all flags
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.post("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/versions/restore", headers=h, json={"key": "string", "version_id": "string"}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/versions/restore", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"key": "string", "version_id": "string"}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## POST /cloud/storage/{id}/buckets/{bucket}/versions/undelete

**Remove a delete marker** · operationId `undeleteStorageObject` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/undeleteStorageObject.json

Removes the latest delete marker of the key, so the previous version becomes current again. `version_id` must be that delete marker.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id (Protected Storage) |
| `bucket` (required) | path | string | Bucket name (full name, with the -code suffix) |

### Request body

| Field | Type | Description |
|---|---|---|
| `key` (required) | string | Object key |
| `version_id` (required) | string | Version id (from versions list) |

### Responses

- **202**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: feature_not_available (standard Object Storage service) or not_actionable
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X POST 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/versions/undelete' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"key": "string", "version_id": "string"}'
```
```bash
gyro storage versions undelete 12345 --help    # all flags
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.post("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/versions/undelete", headers=h, json={"key": "string", "version_id": "string"}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/versions/undelete", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"key": "string", "version_id": "string"}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/{id}/buckets/{bucket}/object-lock

**Default retention of a bucket** · operationId `getStorageBucketObjectLock` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/getStorageBucketObjectLock.json

`object_lock` (set at creation, cannot change) and `default_retention` (null = none): new objects get this retention automatically.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id (Protected Storage) |
| `bucket` (required) | path | string | Bucket name (full name, with the -code suffix) |

### Responses

- **200**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: feature_not_available (standard Object Storage service) or not_actionable
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/object-lock' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage object-lock get 12345
```
```hcl
# resource gyro_storage_bucket_object_lock: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_bucket_object_lock
# Lock every new object version for 30 days (GOVERNANCE: Gyro staff can bypass it on request).
resource "gyro_storage_bucket_object_lock" "archive" {
  service_id = gyro_storage_bucket.archive.service_id
  bucket     = gyro_storage_bucket.archive.full_name
  mode       = "GOVERNANCE"
  days       = 30
}

# COMPLIANCE: nobody (Gyro included) can delete or shorten the lock until it expires.
# resource "gyro_storage_bucket_object_lock" "audit" {
#   service_id             = 789
#   bucket                 = gyro_storage_bucket.audit.full_name
#   mode                   = "COMPLIANCE"
#   days                   = 365
#   acknowledge_compliance = true
# }
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/object-lock", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/object-lock", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## PUT /cloud/storage/{id}/buckets/{bucket}/object-lock

**Set the default retention of a bucket** · operationId `setStorageBucketObjectLock` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/setStorageBucketObjectLock.json

Locked buckets only. `mode` GOVERNANCE or COMPLIANCE with `days` 1-3650, or OFF (new objects get no automatic retention; existing locks stay). GOVERNANCE: only Gyro staff can bypass it on request. COMPLIANCE cannot be shortened, switched to GOVERNANCE or removed by anyone (Gyro included) until it expires; the data is kept and the service keeps being billed until then, even after cancellation. Send `"confirm": true` and `"acknowledge": "COMPLIANCE"`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id (Protected Storage) |
| `bucket` (required) | path | string | Bucket name (full name, with the -code suffix) |

### Request body

| Field | Type | Description |
|---|---|---|
| `mode` (required) | string (GOVERNANCE, COMPLIANCE, OFF) |  |
| `days` | integer | Retention days (not with OFF) |
| `acknowledge` | string (COMPLIANCE) | COMPLIANCE only: type COMPLIANCE to accept that it cannot be removed |
| `confirm` | boolean |  |

### Responses

- **202**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: feature_not_available (standard Object Storage service) or not_actionable
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X PUT 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/object-lock' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode": "GOVERNANCE", "days": 1, "acknowledge": "COMPLIANCE", "confirm": true}'
```
```bash
gyro storage object-lock set 12345 --help    # all flags
```
```hcl
# resource gyro_storage_bucket_object_lock: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_bucket_object_lock
# Lock every new object version for 30 days (GOVERNANCE: Gyro staff can bypass it on request).
resource "gyro_storage_bucket_object_lock" "archive" {
  service_id = gyro_storage_bucket.archive.service_id
  bucket     = gyro_storage_bucket.archive.full_name
  mode       = "GOVERNANCE"
  days       = 30
}

# COMPLIANCE: nobody (Gyro included) can delete or shorten the lock until it expires.
# resource "gyro_storage_bucket_object_lock" "audit" {
#   service_id             = 789
#   bucket                 = gyro_storage_bucket.audit.full_name
#   mode                   = "COMPLIANCE"
#   days                   = 365
#   acknowledge_compliance = true
# }
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.put("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/object-lock", headers=h, json={"mode": "GOVERNANCE", "days": 1, "acknowledge": "COMPLIANCE", "confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/object-lock", {
  method: "PUT",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"mode": "GOVERNANCE", "days": 1, "acknowledge": "COMPLIANCE", "confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/{id}/buckets/{bucket}/objects/retention

**Retention of an object version** · operationId `getStorageObjectRetention` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/getStorageObjectRetention.json

Lock mode, end date and legal hold of one version (`version_id` empty = current version).


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id (Protected Storage) |
| `bucket` (required) | path | string | Bucket name (full name, with the -code suffix) |
| `key` (required) | query | string | Object key |
| `version_id` | query | string | Version id (empty = current) |

### Responses

- **200**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: feature_not_available (standard Object Storage service) or not_actionable
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/retention' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage object retention get 12345
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/retention", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/retention", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## PUT /cloud/storage/{id}/buckets/{bucket}/objects/retention

**Lock an object version** · operationId `setStorageObjectRetention` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/setStorageObjectRetention.json

Sets or extends the retention of one version (`version_id` empty = current) for `days` 1-3650 from now. Retention can only be extended; COMPLIANCE cannot be switched to GOVERNANCE. COMPLIANCE cannot be shortened, switched to GOVERNANCE or removed by anyone (Gyro included) until it expires; the data is kept and the service keeps being billed until then, even after cancellation. Send `"confirm": true` and `"acknowledge": "COMPLIANCE"`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id (Protected Storage) |
| `bucket` (required) | path | string | Bucket name (full name, with the -code suffix) |

### Request body

| Field | Type | Description |
|---|---|---|
| `key` (required) | string |  |
| `version_id` | string | Empty = current version |
| `mode` (required) | string (GOVERNANCE, COMPLIANCE) |  |
| `days` (required) | integer |  |
| `acknowledge` | string (COMPLIANCE) | COMPLIANCE only: type COMPLIANCE |
| `confirm` | boolean |  |

### Responses

- **202**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: feature_not_available (standard Object Storage service) or not_actionable
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X PUT 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/retention' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"key": "string", "version_id": "string", "mode": "GOVERNANCE", "days": 1, "acknowledge": "COMPLIANCE", "confirm": true}'
```
```bash
gyro storage object retention set 12345 --help    # all flags
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.put("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/retention", headers=h, json={"key": "string", "version_id": "string", "mode": "GOVERNANCE", "days": 1, "acknowledge": "COMPLIANCE", "confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/retention", {
  method: "PUT",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"key": "string", "version_id": "string", "mode": "GOVERNANCE", "days": 1, "acknowledge": "COMPLIANCE", "confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/{id}/buckets/{bucket}/objects/legal-hold

**Legal hold of an object version** · operationId `getStorageObjectLegalHold` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/getStorageObjectLegalHold.json

`legal_hold` true = the version cannot be deleted until the hold is removed (no end date).


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id (Protected Storage) |
| `bucket` (required) | path | string | Bucket name (full name, with the -code suffix) |
| `key` (required) | query | string | Object key |
| `version_id` | query | string | Version id (empty = current) |

### Responses

- **200**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: feature_not_available (standard Object Storage service) or not_actionable
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/legal-hold' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage object legal-hold get 12345
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/legal-hold", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/legal-hold", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## PUT /cloud/storage/{id}/buckets/{bucket}/objects/legal-hold

**Place or remove a legal hold** · operationId `setStorageObjectLegalHold` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/setStorageObjectLegalHold.json

`status` on / off (locked buckets only). A legal hold blocks deletion with no end date, independent of retention; you can remove it yourself.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id (Protected Storage) |
| `bucket` (required) | path | string | Bucket name (full name, with the -code suffix) |

### Request body

| Field | Type | Description |
|---|---|---|
| `key` (required) | string |  |
| `version_id` | string | Empty = current version |
| `status` (required) | string (on, off) |  |

### Responses

- **202**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: feature_not_available (standard Object Storage service) or not_actionable
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X PUT 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/legal-hold' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"key": "string", "version_id": "string", "status": "on"}'
```
```bash
gyro storage object legal-hold set 12345 --help    # all flags
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.put("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/legal-hold", headers=h, json={"key": "string", "version_id": "string", "status": "on"}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/objects/legal-hold", {
  method: "PUT",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"key": "string", "version_id": "string", "status": "on"}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/{id}/buckets/{bucket}/lifecycle/noncurrent

**Old version expiry of a bucket** · operationId `getStorageNoncurrentExpiration` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/getStorageNoncurrentExpiration.json

Days after which non-current versions are deleted (new buckets: 30). 0 / null = kept until you delete them (they count toward the quota).


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id (Protected Storage) |
| `bucket` (required) | path | string | Bucket name (full name, with the -code suffix) |

### Responses

- **200**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: feature_not_available (standard Object Storage service) or not_actionable
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/lifecycle/noncurrent' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage lifecycle noncurrent get 12345
```
```hcl
# resource gyro_storage_bucket_noncurrent_expiration: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_bucket_noncurrent_expiration
# Delete old (non-current) versions 7 days after they are replaced or deleted.
resource "gyro_storage_bucket_noncurrent_expiration" "archive" {
  service_id = gyro_storage_bucket.archive.service_id
  bucket     = gyro_storage_bucket.archive.full_name
  days       = 7
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/lifecycle/noncurrent", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/lifecycle/noncurrent", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## PUT /cloud/storage/{id}/buckets/{bucket}/lifecycle/noncurrent

**Set old version expiry** · operationId `setStorageNoncurrentExpiration` · scope `services_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/setStorageNoncurrentExpiration.json

`days` 1-3650, or 0 to keep old versions. Locked versions are deleted only after their retention ends. Other lifecycle rules of the bucket are kept.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Service id (Protected Storage) |
| `bucket` (required) | path | string | Bucket name (full name, with the -code suffix) |

### Request body

| Field | Type | Description |
|---|---|---|
| `days` (required) | integer |  |

### Responses

- **202**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: feature_not_available (standard Object Storage service) or not_actionable
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X PUT 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/lifecycle/noncurrent' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"days": 1}'
```
```bash
gyro storage lifecycle noncurrent set 12345 --help    # all flags
```
```hcl
# resource gyro_storage_bucket_noncurrent_expiration: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage_bucket_noncurrent_expiration
# Delete old (non-current) versions 7 days after they are replaced or deleted.
resource "gyro_storage_bucket_noncurrent_expiration" "archive" {
  service_id = gyro_storage_bucket.archive.service_id
  bucket     = gyro_storage_bucket.archive.full_name
  days       = 7
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.put("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/lifecycle/noncurrent", headers=h, json={"days": 1}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345/buckets/1/lifecycle/noncurrent", {
  method: "PUT",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"days": 1}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/servers/{id}/storage-mounts

**List object storage mounts of a server** · operationId `listStorageMounts` · scope `cloud_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/listStorageMounts.json

Mount a bucket of your Gyro Object Storage service on a Linux server (systemd) as a folder (rclone FUSE mount with a local write cache, starts on boot, reconnects). Each mount gets its own access key limited to that one bucket (read-only or read/write); it does not use your key limit and is deleted with the mount. Gyro Cloud Istanbul (tr-ist-1) servers apply it automatically (`automatic: true`); other servers run `install_command` once. Free; S3 traffic counts towards the storage service. Protected Storage buckets: read-only by default (2.20.0). Supported servers: Gyro Cloud (Istanbul and EU), Turkey VDS, dedicated servers.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Server service id |

### Responses

- **200**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: server_not_active | storage_not_active | limit | mount_point_in_use | mount_point_nested | not_live
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)
- **429**: rate_limited
- **503**: storage_mount_unavailable

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro server storage-mount list 12345
```
```hcl
# resource gyro_server_storage_mount: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/server_storage_mount
resource "gyro_server_storage_mount" "backups" {
  server_id          = gyro_server.web.id
  storage_service_id = 17546
  bucket             = "backups"
  mount_point        = "/mnt/backups"
}

# On servers outside tr-ist-1 run the one-time command on the server (sensitive, valid 60 minutes):
output "mount_command" {
  value     = gyro_server_storage_mount.backups.automatic ? "" : gyro_server_storage_mount.backups.install_command
  sensitive = true
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## POST /cloud/servers/{id}/storage-mounts

**Mount a bucket on the server** · operationId `createStorageMount` · scope `cloud_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/createStorageMount.json

Mount a bucket of your Gyro Object Storage service on a Linux server (systemd) as a folder (rclone FUSE mount with a local write cache, starts on boot, reconnects). Each mount gets its own access key limited to that one bucket (read-only or read/write); it does not use your key limit and is deleted with the mount. Gyro Cloud Istanbul (tr-ist-1) servers apply it automatically (`automatic: true`); other servers run `install_command` once. Free; S3 traffic counts towards the storage service. Supported servers: Gyro Cloud (Istanbul and EU), Turkey VDS, dedicated servers. The answer carries `install_command` (single use, 60 minutes). **Protected Storage (2.20.0):** supported; read-only unless `read_only: false` together with `accept_versioning: true`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Server service id |

### Request body

| Field | Type | Description |
|---|---|---|
| `storage_service_id` (required) | integer | your active Object Storage service |
| `bucket` (required) | string | bucket name in that service |
| `mount_point` | string | absolute path under /mnt, /srv, /data, /media, /opt, /home or /var/www; default /mnt/<bucket>. Must be empty or missing on the server. |
| `read_only` | boolean | Default: false on Object Storage, **true on Protected Storage**. |
| `accept_versioning` | boolean | Protected Storage read/write only: confirms that every write/overwrite creates a new version, deletes only add a delete marker, locked files cannot be deleted and used space/cost grow. Without it: 422 versioning_ack_required. |

### Responses

- **202**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: server_not_active | storage_not_active | limit | mount_point_in_use | mount_point_nested | not_live
- **422**: invalid_bucket | invalid_mount_point | versioning_ack_required (Protected Storage read/write without accept_versioning) | storage_error
- **429**: rate_limited
- **503**: storage_mount_unavailable

### Examples

```bash
curl -s -X POST 'https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"storage_service_id": 1, "bucket": "backups", "mount_point": "/mnt/backups", "read_only": true, "accept_versioning": true}'
```
```bash
gyro server storage-mount create 12345 --help    # all flags
```
```hcl
# resource gyro_server_storage_mount: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/server_storage_mount
resource "gyro_server_storage_mount" "backups" {
  server_id          = gyro_server.web.id
  storage_service_id = 17546
  bucket             = "backups"
  mount_point        = "/mnt/backups"
}

# On servers outside tr-ist-1 run the one-time command on the server (sensitive, valid 60 minutes):
output "mount_command" {
  value     = gyro_server_storage_mount.backups.automatic ? "" : gyro_server_storage_mount.backups.install_command
  sensitive = true
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.post("https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts", headers=h, json={"storage_service_id": 1, "bucket": "backups", "mount_point": "/mnt/backups", "read_only": True, "accept_versioning": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({"storage_service_id": 1, "bucket": "backups", "mount_point": "/mnt/backups", "read_only": true, "accept_versioning": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/servers/{id}/storage-mounts/{mid}

**Get one storage mount** · operationId `getStorageMount` · scope `cloud_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/getStorageMount.json




### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Server service id |
| `mid` (required) | path | integer | Storage mount id |

### Responses

- **200**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: server_not_active | storage_not_active | limit | mount_point_in_use | mount_point_nested | not_live
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)
- **429**: rate_limited
- **503**: storage_mount_unavailable

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts/1' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro server storage-mount get 12345
```
```hcl
# resource gyro_server_storage_mount: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/server_storage_mount
resource "gyro_server_storage_mount" "backups" {
  server_id          = gyro_server.web.id
  storage_service_id = 17546
  bucket             = "backups"
  mount_point        = "/mnt/backups"
}

# On servers outside tr-ist-1 run the one-time command on the server (sensitive, valid 60 minutes):
output "mount_command" {
  value     = gyro_server_storage_mount.backups.automatic ? "" : gyro_server_storage_mount.backups.install_command
  sensitive = true
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts/1", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts/1", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## DELETE /cloud/servers/{id}/storage-mounts/{mid}

**Remove a storage mount** · operationId `deleteStorageMount` · scope `cloud_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/deleteStorageMount.json

The mount key is deleted at once (the server can no longer reach the bucket; files in the bucket are kept). tr-ist-1 servers remove the unit automatically; on others run `remove_command`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Server service id |
| `mid` (required) | path | integer | Storage mount id |

### Responses

- **202**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: server_not_active | storage_not_active | limit | mount_point_in_use | mount_point_nested | not_live
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)
- **429**: rate_limited
- **503**: storage_mount_unavailable

### Examples

```bash
curl -s -X DELETE 'https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts/1' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro server storage-mount remove 12345
```
```hcl
# resource gyro_server_storage_mount: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/server_storage_mount
resource "gyro_server_storage_mount" "backups" {
  server_id          = gyro_server.web.id
  storage_service_id = 17546
  bucket             = "backups"
  mount_point        = "/mnt/backups"
}

# On servers outside tr-ist-1 run the one-time command on the server (sensitive, valid 60 minutes):
output "mount_command" {
  value     = gyro_server_storage_mount.backups.automatic ? "" : gyro_server_storage_mount.backups.install_command
  sensitive = true
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.delete("https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts/1", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts/1", {
  method: "DELETE",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## POST /cloud/servers/{id}/storage-mounts/{mid}/command

**New install command (rotates the mount key)** · operationId `renewStorageMountCommand` · scope `cloud_write` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/renewStorageMountCommand.json

Creates a new access key for the mount and a new single-use install command (the old key stops at once). Use after reinstalling the server or when the command expired.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Server service id |
| `mid` (required) | path | integer | Storage mount id |

### Responses

- **202**: OK
- **404**: Not found — also returned for objects owned by someone else
- **409**: server_not_active | storage_not_active | limit | mount_point_in_use | mount_point_nested | not_live
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)
- **429**: rate_limited
- **503**: storage_mount_unavailable

### Examples

```bash
curl -s -X POST 'https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts/1/command' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro server storage-mount command 12345
```
```hcl
# resource gyro_server_storage_mount: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/server_storage_mount
resource "gyro_server_storage_mount" "backups" {
  server_id          = gyro_server.web.id
  storage_service_id = 17546
  bucket             = "backups"
  mount_point        = "/mnt/backups"
}

# On servers outside tr-ist-1 run the one-time command on the server (sensitive, valid 60 minutes):
output "mount_command" {
  value     = gyro_server_storage_mount.backups.automatic ? "" : gyro_server_storage_mount.backups.install_command
  sensitive = true
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.post("https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts/1/command", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/servers/12345/storage-mounts/1/command", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/plans

**List Object Storage plans** · operationId `listStoragePlans` · scope `cloud_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/listStoragePlans.json

Orderable Object Storage (`standard-*`) and Protected Storage (`protected-*`) plans with limits, prices per billing cycle (fixed EUR, VAT excluded) and add-ons: extra traffic packs (`1tb`, `5tb`, one-time, this calendar month) and object lock buckets (protected plans). `available: false` = no capacity right now.


### Responses

- **200**: OK

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/plans' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage plans
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/plans", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/plans", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage

**List storage services** · operationId `listStorageServices` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/listStorageServices.json

Object Storage and Protected Storage services of the account (same filters and paging as GET /cloud/services).


### Responses

- **200**: OK

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro api request GET /cloud/storage
```
```hcl
# resource gyro_storage: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage
# Object Storage (S3) 1 TB, billed monthly from the account balance
resource "gyro_storage" "yedek" {
  plan  = "standard-1tb"
  cycle = "monthly"
  name  = "gyro-proje-yedek"

  # destroy deletes every bucket and object of the service
  lifecycle {
    prevent_destroy = true
  }
}

# Protected Storage with 2 object lock (WORM) buckets
resource "gyro_storage" "worm" {
  plan                = "protected-250gb"
  cycle               = "annually"
  object_lock_buckets = 2
}

resource "gyro_storage_bucket" "dosyalar" {
  service_id = gyro_storage.yedek.service_id
  name       = "gyro-proje-dosyalar"
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## POST /cloud/storage

**Order Object Storage (S3)** · operationId `createStorageServices` · scope `cloud_order` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/createStorageServices.json

Orders 1..N storage services of a plan in ONE order (`quantity`, default 1, max 25) and pays from the account balance (never a stored card). Billed per `cycle` (monthly | quarterly | semiannually | annually; fixed EUR prices charged in the account currency) and renewed from the balance like other services. `extra_traffic` (`1tb` | `5tb`) adds a one-time traffic pack for this month; `object_lock_buckets` (protected plans, 1-50) buys object lock rights (billed every cycle). `name` (with `{n}`), `tags` and `project_id` label the services. Send `"dry_run": true` for a quote; a real order needs `"confirm": true` and an `Idempotency-Key` header. The first bucket and the first access key are created automatically within about a minute (status `inprocess` -> `active`); then use `/cloud/storage/{id}/buckets`, `/cloud/storage/{id}/keys` and `/cloud/storage/{id}/connect`. Errors: 402 `insufficient_balance`, 409 `capacity_unavailable`, 422 `confirm_required` (with `quote`), `product_not_orderable`, `cycle_invalid`, `invalid_extra_traffic`, `invalid_object_lock`, `invalid_quantity`; 429 `daily_limit`.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `Idempotency-Key` (required) | header | string | Unique per logical request (e.g. a UUID). Replays within 24 hours return the stored answer. |

### Request body

| Field | Type | Description |
|---|---|---|
| `plan` | string | plan from GET /cloud/storage/plans |
| `product_id` | integer | product id instead of plan |
| `cycle` | string (monthly, quarterly, semiannually, annually) | billing cycle |
| `quantity` | integer | services to create in one order (--count), max 25 |
| `extra_traffic` | string (1tb, 5tb) | one-time traffic pack for this calendar month |
| `object_lock_buckets` | integer | protected plans: object lock bucket rights (billed every cycle) |
| `name` | string | name (nickname); {n} = number in a batch |
| `tags` | array<string> |  |
| `project_id` | integer / null | project (0 = default project) |
| `end_customer_id` | integer | reseller main key: assign to this end-customer |
| `confirm` | boolean |  |
| `dry_run` | boolean |  |

### Responses

- **200**: Dry-run quote
- **201**: Ordered and paid; services are being set up
- **402**: Account balance does not cover the order (`insufficient_balance`)
- **403**: Key lacks the scope, IP not allowed, or feature disabled
- **409**: State conflict (`server_locked`, `not_actionable`, `ssh_key_exists`, `server_limit`, `idempotency_in_flight`)
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)
- **429**: Too many requests (`rate_limited`); see the `Retry-After` and `X-RateLimit-*` headers

### Examples

```bash
curl -s -X POST 'https://www.gyrohosting.com/api/v1/client/cloud/storage' \
  -H "Authorization: Bearer $GYRO_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{"plan": "standard-1tb", "cycle": "monthly", "name": "gyro-proje-s3", "extra_traffic": "1tb", "confirm": true}'
```
```bash
gyro storage create --help    # all flags
```
```hcl
# resource gyro_storage: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage
# Object Storage (S3) 1 TB, billed monthly from the account balance
resource "gyro_storage" "yedek" {
  plan  = "standard-1tb"
  cycle = "monthly"
  name  = "gyro-proje-yedek"

  # destroy deletes every bucket and object of the service
  lifecycle {
    prevent_destroy = true
  }
}

# Protected Storage with 2 object lock (WORM) buckets
resource "gyro_storage" "worm" {
  plan                = "protected-250gb"
  cycle               = "annually"
  object_lock_buckets = 2
}

resource "gyro_storage_bucket" "dosyalar" {
  service_id = gyro_storage.yedek.service_id
  name       = "gyro-proje-dosyalar"
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
h["Idempotency-Key"] = str(uuid.uuid4())
r = requests.post("https://www.gyrohosting.com/api/v1/client/cloud/storage", headers=h, json={"plan": "standard-1tb", "cycle": "monthly", "name": "gyro-proje-s3", "extra_traffic": "1tb", "confirm": True}, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage", {
  method: "POST",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
    "Content-Type": "application/json",
    "Idempotency-Key": crypto.randomUUID(),
  },
  body: JSON.stringify({"plan": "standard-1tb", "cycle": "monthly", "name": "gyro-proje-s3", "extra_traffic": "1tb", "confirm": true}),
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## GET /cloud/storage/{id}

**Get a storage service** · operationId `getStorageService` · scope `services_read` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/getStorageService.json

Service row plus `storage` (plan, kind, limits, S3 endpoint, region).


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Storage service id |

### Responses

- **200**: OK
- **404**: Not found — also returned for objects owned by someone else

### Examples

```bash
curl -s 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage get 12345
```
```hcl
# resource gyro_storage: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage
# Object Storage (S3) 1 TB, billed monthly from the account balance
resource "gyro_storage" "yedek" {
  plan  = "standard-1tb"
  cycle = "monthly"
  name  = "gyro-proje-yedek"

  # destroy deletes every bucket and object of the service
  lifecycle {
    prevent_destroy = true
  }
}

# Protected Storage with 2 object lock (WORM) buckets
resource "gyro_storage" "worm" {
  plan                = "protected-250gb"
  cycle               = "annually"
  object_lock_buckets = 2
}

resource "gyro_storage_bucket" "dosyalar" {
  service_id = gyro_storage.yedek.service_id
  name       = "gyro-proje-dosyalar"
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.get("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345", {
  method: "GET",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```


## DELETE /cloud/storage/{id}

**Cancel a storage service** · operationId `deleteStorageService` · scope `cloud_delete` · OpenAPI fragment: https://www.gyrohosting.com/developers/spec/ops/deleteStorageService.json

`confirm=true` is required. `immediate=true` cancels now: access keys, website and custom domains stop within about a minute and every bucket and object is deleted permanently within minutes (cannot be undone; Protected Storage versions under a COMPLIANCE lock or legal hold are kept until the lock ends and stay billable). Without `immediate` a cancellation request is filed and the service keeps running until it is processed.


### Parameters

| Field | in | Type | Description |
|---|---|---|---|
| `id` (required) | path | integer | Storage service id |
| `confirm` (required) | query | boolean |  |
| `immediate` | query | boolean |  |

### Responses

- **202**: cancelling | cancellation_requested
- **404**: Not found — also returned for objects owned by someone else
- **422**: Validation or business rule failure (`confirm_required`, `invalid_image`, `tool_action_failed` ...)

### Examples

```bash
curl -s -X DELETE 'https://www.gyrohosting.com/api/v1/client/cloud/storage/12345' \
  -H "Authorization: Bearer $GYRO_TOKEN"
```
```bash
gyro storage delete 12345
```
```hcl
# resource gyro_storage: https://registry.terraform.io/providers/gyro-hosting/gyro/latest/docs/resources/storage
# Object Storage (S3) 1 TB, billed monthly from the account balance
resource "gyro_storage" "yedek" {
  plan  = "standard-1tb"
  cycle = "monthly"
  name  = "gyro-proje-yedek"

  # destroy deletes every bucket and object of the service
  lifecycle {
    prevent_destroy = true
  }
}

# Protected Storage with 2 object lock (WORM) buckets
resource "gyro_storage" "worm" {
  plan                = "protected-250gb"
  cycle               = "annually"
  object_lock_buckets = 2
}

resource "gyro_storage_bucket" "dosyalar" {
  service_id = gyro_storage.yedek.service_id
  name       = "gyro-proje-dosyalar"
}
```
```python
import os, uuid, requests

h = {"Authorization": "Bearer " + os.environ["GYRO_TOKEN"]}
r = requests.delete("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345", headers=h, timeout=60)
r.raise_for_status()
print(r.json()["data"])
```
```js
const res = await fetch("https://www.gyrohosting.com/api/v1/client/cloud/storage/12345", {
  method: "DELETE",
  headers: {
    "Authorization": `Bearer ${process.env.GYRO_TOKEN}`,
  },
});
const { data, error } = await res.json();
if (!res.ok) throw new Error(`${error.code}: ${error.message}`);
```
