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

# Cloudinary, S3, R2, and S2 migrations

> Inventory, dry-run, import, deduplicate, retry, cancel, and report durable migrations from Cloudinary or S3-compatible storage into StackShift Assets.

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

## Goal

Move a provider library into Assets with deterministic folder mapping, checksum evidence, restart-safe progress, and an item-level audit report.

## Prerequisites

* A verified Cloudinary, S3-compatible, or native S2 source connection
* A destination Assets bucket and its UUID
* An `assets:process` token to start, cancel, or retry and `assets:read` to inspect or download reports

## Workflow

<Steps>
  <Step>
    Run a dry migration with the intended source prefix and conflict policy.
  </Step>

  <Step>
    Wait for inventory to complete, then inspect summary counts and the JSON or CSV item report.
  </Step>

  <Step>
    Start a non-dry migration with the same mapping once duplicate, conflict, unsupported-object, and pending-checksum outcomes are acceptable.
  </Step>

  <Step>
    Follow persisted phase, item, and byte counters. Cancel cooperatively or retry only terminal failed items when needed.
  </Step>

  <Step>
    Retain the report as migration evidence and verify a sample of imported assets through StackShift CDN URLs.
  </Step>
</Steps>

## What discovery preserves

* Cloudinary: paginated `image`, `video`, and `raw` upload resources; the public ID becomes the folder/key, the format becomes the extension when needed, and tags, context, structured metadata, resource type, source URL, and source identity remain in migration evidence.
* Cloudinary imports originals only. StackShift regenerates transforms, thumbnails, video outputs, and other derivatives under the destination bucket policy.
* S3-compatible and S2: recursive objects beneath `source_prefix`; relative keys preserve folder structure, and content type, cache control, object metadata, object tags, size, provider checksum when available, and source identity are recorded.
* Discovery is paginated and idempotent by `(migration, source_identity)`, so worker restarts do not duplicate inventory rows.

## Create Cloudinary and S2 source connections

Cloudinary configuration keeps the cloud name and API key as non-secret connection data and encrypts the API secret. A native S2 import source is a `stackshift_s2` connection that references an owned bucket UUID and carries no credentials.

<CodeGroup>
  ```ts Cloudinary source theme={null}
  const cloudinary = await stackshift.assets.connections.create({
    provider: 'cloudinary',
    name: 'Legacy Cloudinary library',
    configuration: {
      cloud_name: 'acme',
      api_key: process.env.CLOUDINARY_API_KEY!,
    },
    credentials: {
      api_secret: process.env.CLOUDINARY_API_SECRET!,
    },
  })
  await stackshift.assets.connections.verify(cloudinary.id)
  ```

  ```ts Native S2 source theme={null}
  const s2Source = await stackshift.assets.connections.create({
    provider: 'stackshift_s2',
    name: 'Existing S2 library',
    s2_bucket_id: ownedS2Bucket.id,
  })
  await stackshift.assets.connections.verify(s2Source.id)
  ```
</CodeGroup>

## Dry-run semantics

A dry run finishes after inventory. It may issue list and metadata/tag reads, but it does not download objects just to manufacture a missing checksum and does not create assets. The item report is the source of truth for what was and was not knowable before execution.

* `duplicate_checksum`: a known SHA-256 already maps to an asset in the same Assets space.
* `key_conflict`: the destination bucket already contains the proposed logical key.
* `unsupported_object`: the known size or MIME type violates destination bucket policy.
* `pending_execution`: checksum evidence requires streaming the object during the real run.
* `inventory_lookup_failed`: StackShift could not safely classify an item and records an error instead of guessing.

## Start and follow a migration

```ts theme={null}
const dryRun = await stackshift.assets.migrations.start({
  kind: 'import',
  source_connection_id: source.id,
  destination_bucket_id: destination.id,
  source_prefix: 'legacy/library',
  conflict_policy: 'skip',
  dry_run: true,
})

let current = await stackshift.assets.migrations.get(dryRun.id)
while (current.status === 'queued' || current.status === 'running') {
  await new Promise((resolve) => setTimeout(resolve, 2000))
  current = await stackshift.assets.migrations.get(dryRun.id)
}
const report = await stackshift.assets.migrations.downloadReport(current.id, 'json')
```

```bash theme={null}
stackshift asset migration-start --wait --data '{
  "kind":"import",
  "source_connection_id":"SOURCE_CONNECTION_UUID",
  "destination_bucket_id":"ASSETS_BUCKET_UUID",
  "source_prefix":"legacy/library",
  "conflict_policy":"skip",
  "dry_run":true
}' --output json
```

## Checksum deduplication and key conflicts

* Execution streams the source through bounded scratch space and calculates SHA-256 before asset commit.
* Deduplication is Assets-space-wide. Duplicate content is skipped, `mapped_asset_id` points to the existing asset, and no alias asset is created.
* `skip` is the default conflict policy and preserves the existing logical asset.
* `new_version` replaces the compatible existing logical asset through revision-aware versioning. Exact normalized MIME compatibility is required.
* `rename` appends the first eight hex characters of the source-identity SHA-256 before the extension, then a stable numeric suffix if that name is also occupied.

## Progress, retries, cancellation, and reports

* Migration states are `queued`, `running`, `completed`, `failed`, and `canceled`; phases begin at `inventory`, continue through `transfer`, and finish at `complete`.
* Persisted counters include total/processed/imported/skipped/failed items and total/processed bytes. They remain available independently of the worker process.
* Each item stores attempt count, maximum attempts, checksum state, mapped asset, warning/error code and message, timestamps, and source/destination identity. Automatic transfer retries use bounded backoff.
* Cancellation is cooperative: active work stops, unclaimed items become canceled, incomplete multipart work is aborted, and scratch data is removed. Start a new migration to continue a canceled migration.
* JSON contains the complete migration and item objects. CSV columns are `source_identity`, `source_key`, `destination_key`, `status`, `checksum_sha256`, `mapped_asset_id`, `warning_code`, `error_code`, and `error_message`.

```bash theme={null}
stackshift asset migration-report MIGRATION_UUID --query format=csv --download ./migration.csv
stackshift asset migration-item-retry MIGRATION_UUID ITEM_UUID --output json
stackshift asset migration-cancel MIGRATION_UUID --yes --output json
```

## Expected result

<Check>
  Every discovered source identity ends in an imported, skipped, failed, or canceled item with checksum and mapped-asset evidence where available.
</Check>

## Common failures

<Warning>
  * The source connection is not verified or lost provider permissions after verification.
  * A dry run reports `pending_execution`; the source did not expose SHA-256 and the dry run intentionally did not download bytes.
  * `new_version` skips a key conflict because the existing and incoming normalized MIME types are incompatible.
  * A worker or provider interruption exhausts automatic attempts; retry the specific failed item after fixing the cause.
</Warning>

## Related guides

<CardGroup cols={2}>
  <Card title="Storage connections, BYOB, and StackShift S2" href="/assets/storage-connections-and-byob">
    Configure AWS S3, Cloudflare R2, Wasabi, MinIO, generic S3, or native StackShift S2 without exposing customer storage credentials or changing delivery URLs.
  </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="Upload UX and DAM" href="/assets/upload-ux-and-dam">
    Build a durable upload and digital-asset-management workflow with resumable sessions, revision-safe mutations, search, collections, webhooks, and usage summaries.
  </Card>
</CardGroup>
