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

# Storage connections, BYOB, and StackShift S2

> Configure AWS S3, Cloudflare R2, Wasabi, MinIO, generic S3, or native StackShift S2 without exposing customer storage credentials or changing delivery URLs.

<Tip>
  **Live.** This area is documented as current, user-reliable behavior.
</Tip>

## Goal

Put new Assets objects and derivatives in the selected customer-owned backend, verify access safely, and relocate historical objects without an availability gap.

## Prerequisites

* An Assets bucket and its current revision
* An `assets:admin` token for connection administration and `assets:write` for bucket storage changes
* Bucket and prefix permissions at the external provider, or an owned StackShift S2 bucket

## Workflow

<Steps>
  <Step>
    Create a provider connection. Prefer AWS AssumeRole; use static credentials only when the provider cannot issue a role.
  </Step>

  <Step>
    For AWS, install the returned trust and permission policies with the connection external ID. For S2, select or create an owned bucket without creating an access key.
  </Step>

  <Step>
    Verify the connection. StackShift probes the reserved verification prefix and records either `verified` or a redacted failure.
  </Step>

  <Step>
    Update the Assets bucket storage configuration with its current revision. New originals, versions, derivatives, and video outputs use the new backend immediately.
  </Step>

  <Step>
    Run a relocation dry run, then execute relocation if historical objects must move. Existing objects remain on their recorded backend until each verified cutover commits.
  </Step>
</Steps>

## Storage modes and provider identifiers

* `managed`: the default for existing and new Assets buckets unless you explicitly select another mode.
* `byob`: an external S3-compatible destination referenced by `connection_id`.
* `stackshift_s2`: a native S2 destination referenced directly by `s2_bucket_id`; Assets stores no S2 access key.
* Connection provider values are `aws_s3`, `r2`, `wasabi`, `minio`, `s3`, `stackshift_s2`, and `cloudinary`. Cloudinary is an import source only and cannot be a bucket destination.
* R2, Wasabi, MinIO, and generic S3 require explicit static credentials and a public HTTPS endpoint. Endpoint-backed providers use path-style requests.

## AWS AssumeRole connection

Create the connection first. Its response includes a unique `external_id`, `trust_policy`, and prefix-scoped `permission_policy`. Install those returned policies in the customer AWS account, then verify the connection. The role session is short-lived and names the connection; no static key is stored.

```ts theme={null}
const connection = await stackshift.assets.connections.create({
  provider: 'aws_s3',
  name: 'Production media',
  configuration: {
    bucket: 'acme-media',
    region: 'us-east-1',
    prefix: 'stackshift-assets',
    role_arn: 'arn:aws:iam::123456789012:role/StackshiftAssets',
    auth_type: 'assume_role',
  },
})

console.log(connection.external_id)
console.log(connection.trust_policy, connection.permission_policy)
await stackshift.assets.connections.verify(connection.id)
```

## Required external permissions and verification

* Object scope: `GetObject`, `PutObject`, `GetObjectTagging`, `DeleteObject`, and `AbortMultipartUpload` beneath the configured prefix.
* Bucket scope: `ListBucket` and `ListBucketMultipartUploads`, constrained to the configured prefix.
* Verification performs list, write, read, metadata/head plus tags, delete, multipart-create/upload, and multipart-abort beneath `.stackshift-assets-verification`.
* The probe always attempts cleanup. A cleanup failure is part of the verification failure and should be resolved before production writes.
* Stored credentials are envelope-encrypted with connection-specific authenticated data. Create and update responses never return the secret payload.

<CodeGroup>
  ```json Representative AWS trust policy theme={null}
  {
    "Version": "2012-10-17",
    "Statement": [{
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::STACKSHIFT_ACCOUNT_ID:role/stackshift-assets-control-plane"
      },
      "Action": "sts:AssumeRole",
      "Condition": {
        "StringEquals": {
          "sts:ExternalId": "CONNECTION_EXTERNAL_ID"
        }
      }
    }]
  }
  ```

  ```json Prefix permission policy theme={null}
  {
    "Version": "2012-10-17",
    "Statement": [
      {
        "Effect": "Allow",
        "Action": [
          "s3:GetObject", "s3:PutObject", "s3:GetObjectTagging",
          "s3:DeleteObject", "s3:AbortMultipartUpload"
        ],
        "Resource": "arn:aws:s3:::acme-media/stackshift-assets/*"
      },
      {
        "Effect": "Allow",
        "Action": ["s3:ListBucket", "s3:ListBucketMultipartUploads"],
        "Resource": "arn:aws:s3:::acme-media",
        "Condition": {
          "StringLike": { "s3:prefix": "stackshift-assets/*" }
        }
      }
    ]
  }
  ```
</CodeGroup>

## Static S3-compatible connection

Use the API provider value `r2` for Cloudflare R2. Keep the access key in the `credentials` object, never in `configuration`. Updating credentials replaces the encrypted secret; listing or getting the connection returns only non-secret configuration and verification state.

```bash theme={null}
stackshift asset connection-create --data '{
  "provider":"r2",
  "name":"R2 media",
  "configuration":{
    "bucket":"media",
    "region":"auto",
    "endpoint":"https://ACCOUNT_ID.r2.cloudflarestorage.com",
    "prefix":"assets",
    "auth_type":"static",
    "path_style":true
  },
  "credentials":{
    "access_key_id":"REDACTED",
    "secret_access_key":"REDACTED"
  }
}' --output json
```

## Provider endpoint settings

* Cloudflare R2: provider `r2`, region `auto`, endpoint `https://ACCOUNT_ID.r2.cloudflarestorage.com`, static auth, and path-style requests.
* Wasabi: provider `wasabi`, the bucket’s Wasabi region, endpoint such as `https://s3.us-east-1.wasabisys.com`, and static auth.
* MinIO: provider `minio`, the deployment’s configured region (commonly `us-east-1`), a publicly reachable TLS origin such as `https://objects.example.com`, and static auth.
* Generic S3: provider `s3`, the provider’s region and public HTTPS origin, static auth, and path-style requests.
* AWS static fallback: provider `aws_s3`, `auth_type: static`, standard AWS region, no custom endpoint, and a narrowly scoped access key. AssumeRole remains preferred.
* Do not include a bucket path in `endpoint`; put the bucket in `configuration.bucket` and any namespace in `configuration.prefix`.

## Native StackShift S2

List or create an owned S2 bucket through the Assets storage client, then attach its UUID. Ownership is checked against the Assets workspace. Internally, Assets uses a bucket-and-prefix-scoped S2 principal with put, get, list, delete, and multipart operations. S2 quotas, checksums, encryption, lifecycle, versioning, and conformance remain enforced by the Object Store service.

```ts theme={null}
const s2 = await stackshift.assets.storage.createS2Bucket({
  name: 'asset-originals',
  region: 'us-east-1',
})

await stackshift.assets.storage.configure(bucket.id, bucket.revision, {
  storage_mode: 'stackshift_s2',
  s2_bucket_id: s2.id,
  object_prefix: 'originals',
  derivative_prefix: 'derived',
  auto_extract_text: false,
})
```

## Cutover, relocation, deletion, and billing

* A storage configuration change affects new writes immediately. Historical originals, versions, derivatives, and video outputs keep their own backend reference.
* Relocation streams to the destination, verifies SHA-256, and atomically changes the durable reference. If transfer or verification fails, reads continue from the source.
* Source deletion happens after commit. `source_delete_failed` means delivery already uses the destination; remove the old object after restoring provider access instead of rolling back the reference.
* All backends count toward logical Assets quotas and processing, OCR, and CDN usage. External BYOB and native S2 objects do not accrue Assets managed-storage byte-hours; S2 has its own Object Store accounting.
* External bucket endpoints and credentials never appear in asset URLs. CDN/origin routing resolves each object’s recorded backend server-side.

## Expected result

<Check>
  The Assets bucket writes through the selected backend while all public and private delivery continues through the StackShift CDN gateway.
</Check>

## Common failures

<Warning>
  * The connection remains `failed` because list, object tagging, delete, or multipart-abort permission is missing.
  * A generic S3 or MinIO endpoint is rejected because it is not a public HTTPS origin, redirects, or resolves to a private/reserved address.
  * The storage update conflicts because the bucket revision changed; reload the bucket and resubmit with the new revision.
  * Connection or S2 deletion is blocked because an Assets bucket, active migration, upload, relocation, or stored object still references it.
</Warning>

## Related guides

<CardGroup cols={2}>
  <Card title="Cloudinary, S3, R2, and S2 migrations" href="/assets/cloudinary-s3-and-s2-migrations">
    Inventory, dry-run, import, deduplicate, retry, cancel, and report durable migrations from Cloudinary or S3-compatible storage into StackShift Assets.
  </Card>

  <Card title="Assets platform API, SDK, CLI, and operations" href="/assets/platform-api-cli-and-operations">
    A complete interface and recovery reference for storage connections, S2, imports, relocation, OCR, browser capabilities, reports, scopes, events, billing, and durable worker behavior.
  </Card>

  <Card title="Access keys, encryption, isolation, and quotas" href="/object-storage/access-and-security">
    Operate S2 credentials, encryption, visibility, tenant isolation, request limits, and customer-plan quotas safely.
  </Card>
</CardGroup>
