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

# Update S3

> Sets S3 credentials and bucket for storing instance media

**Auth:** `TokenAccount` or `TokenInstance` • **Rate-limit:** `Global` (100/min) • **Idempotent:** yes

## Description

Configures the S3 storage of the instance. `secretKey` is **encrypted at-rest** and never returned. For S3-compatible storage (MinIO, Backblaze, DO Spaces), fill `endpoint` with the URL.

## Examples

### AWS S3

Points storage to official AWS S3: bucket `ryzeapi-media` in `us-east-1`, with `endpoint` empty to use AWS's default domain and prefix `media/myinstance/` to isolate the files.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://ryzeapi.cloud/api/instance/s3/my-instance" \
    -H "token: $Token_Instance" \
    -H "Content-Type: application/json" \
    -d '{
      "enabled": true,
      "region": "us-east-1",
      "bucket": "ryzeapi-media",
      "accessKey": "AKIA...",
      "secretKey": "secret-redacted",
      "endpoint": "",
      "pathPrefix": "media/myinstance/"
    }'
  ```

  ```javascript JavaScript theme={null}
  await fetch("https://ryzeapi.cloud/api/instance/s3/my-instance", {
    method: "POST",
    headers: {
      "token":        process.env.Token_Instance,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      enabled:    true,
      region:     "us-east-1",
      bucket:     "ryzeapi-media",
      accessKey:  "AKIA...",
      secretKey:  "secret-redacted",
      endpoint:   "",
      pathPrefix: "media/myinstance/"
    })
  });
  ```

  ```python Python theme={null}
  import os, requests

  requests.post(
      "https://ryzeapi.cloud/api/instance/s3/my-instance",
      headers={
          "token":        os.environ["Token_Instance"],
          "Content-Type": "application/json"
      },
      json={
          "enabled":    True,
          "region":     "us-east-1",
          "bucket":     "ryzeapi-media",
          "accessKey":  "AKIA...",
          "secretKey":  "secret-redacted",
          "endpoint":   "",
          "pathPrefix": "media/myinstance/"
      }
  )
  ```

  ```go Go theme={null}
  package main

  import (
      "net/http"
      "os"
      "strings"
  )

  func main() {
      body := strings.NewReader(`{
          "enabled":    true,
          "region":     "us-east-1",
          "bucket":     "ryzeapi-media",
          "accessKey":  "AKIA...",
          "secretKey":  "secret-redacted",
          "endpoint":   "",
          "pathPrefix": "media/myinstance/"
      }`)
      req, _ := http.NewRequest("POST", "https://ryzeapi.cloud/api/instance/s3/my-instance", body)
      req.Header.Set("token", os.Getenv("Token_Instance"))
      req.Header.Set("Content-Type", "application/json")
      http.DefaultClient.Do(req)
  }
  ```
</CodeGroup>

### Self-hosted MinIO

Same format as AWS, but with `endpoint` pointing to an internal MinIO (`https://minio.internal.company.com`). The same pattern works for DigitalOcean Spaces, Backblaze B2, and other S3-compatible storage.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://ryzeapi.cloud/api/instance/s3/my-instance" \
    -H "token: $Token_Instance" \
    -H "Content-Type: application/json" \
    -d '{
      "enabled": true,
      "region": "us-east-1",
      "bucket": "whatsapp",
      "endpoint": "https://minio.internal.company.com",
      "accessKey": "minioadmin",
      "secretKey": "minioadmin",
      "pathPrefix": "ryzeapi/"
    }'
  ```

  ```javascript JavaScript theme={null}
  await fetch("https://ryzeapi.cloud/api/instance/s3/my-instance", {
    method: "POST",
    headers: {
      "token":        process.env.Token_Instance,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({
      enabled:    true,
      region:     "us-east-1",
      bucket:     "whatsapp",
      endpoint:   "https://minio.internal.company.com",
      accessKey:  "minioadmin",
      secretKey:  "minioadmin",
      pathPrefix: "ryzeapi/"
    })
  });
  ```

  ```python Python theme={null}
  import os, requests

  requests.post(
      "https://ryzeapi.cloud/api/instance/s3/my-instance",
      headers={
          "token":        os.environ["Token_Instance"],
          "Content-Type": "application/json"
      },
      json={
          "enabled":    True,
          "region":     "us-east-1",
          "bucket":     "whatsapp",
          "endpoint":   "https://minio.internal.company.com",
          "accessKey":  "minioadmin",
          "secretKey":  "minioadmin",
          "pathPrefix": "ryzeapi/"
      }
  )
  ```

  ```go Go theme={null}
  package main

  import (
      "net/http"
      "os"
      "strings"
  )

  func main() {
      body := strings.NewReader(`{
          "enabled":    true,
          "region":     "us-east-1",
          "bucket":     "whatsapp",
          "endpoint":   "https://minio.internal.company.com",
          "accessKey":  "minioadmin",
          "secretKey":  "minioadmin",
          "pathPrefix": "ryzeapi/"
      }`)
      req, _ := http.NewRequest("POST", "https://ryzeapi.cloud/api/instance/s3/my-instance", body)
      req.Header.Set("token", os.Getenv("Token_Instance"))
      req.Header.Set("Content-Type", "application/json")
      http.DefaultClient.Do(req)
  }
  ```
</CodeGroup>

### Disable

Sends only `enabled: false` to disable the storage and **delete** the credentials from the database. To re-enable later, all fields must be sent again.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST "https://ryzeapi.cloud/api/instance/s3/my-instance" \
    -H "token: $Token_Instance" \
    -H "Content-Type: application/json" \
    -d '{"enabled":false}'
  ```

  ```javascript JavaScript theme={null}
  await fetch("https://ryzeapi.cloud/api/instance/s3/my-instance", {
    method: "POST",
    headers: {
      "token":        process.env.Token_Instance,
      "Content-Type": "application/json"
    },
    body: JSON.stringify({ enabled: false })
  });
  ```

  ```python Python theme={null}
  import os, requests

  requests.post(
      "https://ryzeapi.cloud/api/instance/s3/my-instance",
      headers={
          "token":        os.environ["Token_Instance"],
          "Content-Type": "application/json"
      },
      json={"enabled": False}
  )
  ```

  ```go Go theme={null}
  package main

  import (
      "net/http"
      "os"
      "strings"
  )

  func main() {
      body := strings.NewReader(`{"enabled":false}`)
      req, _ := http.NewRequest("POST", "https://ryzeapi.cloud/api/instance/s3/my-instance", body)
      req.Header.Set("token", os.Getenv("Token_Instance"))
      req.Header.Set("Content-Type", "application/json")
      http.DefaultClient.Do(req)
  }
  ```
</CodeGroup>

## Success response

```json 200 OK theme={null}
{
  "success": true,
  "message": "S3 configuration updated",
  "s3": {
    "enabled": true,
    "region": "us-east-1",
    "bucket": "ryzeapi-media",
    "accessKey": "AKIA...",
    "endpoint": "",
    "pathPrefix": "media/myinstance/"
  }
}
```

<Note>
  `secretKey` **does not** appear in the response, the server never returns the key in plaintext.
</Note>

## Path parameters

<ParamField path="instance" type="string" required>
  Instance name.
</ParamField>

## Headers

<ParamField header="token" type="string" required>
  TokenAccount or TokenInstance.
</ParamField>

<ParamField header="Content-Type" type="string" required>
  `application/json`.
</ParamField>

## Request body

<ParamField body="enabled" type="boolean" required>
  Enables/disables the instance S3. `false` clears all fields.
</ParamField>

<ParamField body="region" type="string">
  Region (e.g., `us-east-1`).
</ParamField>

<ParamField body="bucket" type="string">
  Bucket name (must exist; no creation is performed).
</ParamField>

<ParamField body="accessKey" type="string">
  Access Key ID.
</ParamField>

<ParamField body="secretKey" type="string">
  Secret Access Key. Encrypted at-rest.
</ParamField>

<ParamField body="endpoint" type="string">
  Custom endpoint (MinIO, DO Spaces, Backblaze). Empty for official AWS S3.
</ParamField>

<ParamField body="pathPrefix" type="string">
  Path prefix (e.g., `media/myinstance/`).
</ParamField>

## Notes

<Warning>
  **There is no credential testing.** The endpoint saves the config without validating whether the bucket exists or whether the credentials work, the error only appears when the next media upload tries to authenticate (visible in the server logs).
</Warning>

<Note>
  Disabling (`enabled=false`) **deletes** the credentials from the database. To re-enable later, all fields must be sent again.
</Note>

## Errors

| HTTP | `error.message`                         | When                      |
| :--: | --------------------------------------- | ------------------------- |
|  400 | `Invalid request body`                  | Malformed JSON.           |
|  401 | `Invalid token`                         | Token missing or invalid. |
|  404 | `Instance not found`                    | Name does not exist.      |
|  429 | `Rate limit exceeded. Try again later.` | More than 100 req/min.    |
|  500 | `Failed to update S3 configuration`     | Database error.           |

```json theme={null}
{
  "success": false,
  "error": {
    "message": "Failed to update S3 configuration"
  }
}
```
