# Connecting S3 and S3-compatible buckets

Point a Wire container at an S3 bucket (or any S3-compatible storage) and Wire keeps the container in sync with what's there. Add a file at the source, it appears in the container. Change a file, the container picks up the new version. Remove a file, Wire cleans it up. Your bucket is the source of truth; Wire just mirrors the slice of it you want available to your agents.

## What works

Anything that speaks the S3 API works. Wire is tested against:

| Provider | Notes |
|---|---|
| **Amazon S3** | Use a bucket in any region |
| **Cloudflare R2** | Use the S3-compatible endpoint, e.g. `<account>.r2.cloudflarestorage.com` |
| **MinIO** | Self-hosted or hosted, point at your endpoint |
| **Backblaze B2** | Use the S3-compatible endpoint, e.g. `s3.us-west-002.backblazeb2.com` |
| **Wasabi** | e.g. `s3.wasabisys.com` |
| **DigitalOcean Spaces, Linode Object Storage, Cloudian, etc.** | If it speaks the S3 API, it works |

If a provider isn't on this list but exposes the S3 API, the **Custom endpoint** option below will get you connected.

## How it's organized

Wire splits the connection in two:

1. **Connector** is the credential. Lives at the **organization** level. Connect a bucket once, give it a label, done. Anyone with permission can attach it to their containers without re-entering the access key.
2. **Source** is the slice of a bucket that flows into a specific container. Pick a bucket and an optional prefix. One connector can feed many containers, each with its own scope.

This lets a team share a single set of credentials while each container only sees the prefix that's relevant to it.

## Permissions

| Action | Who can do it |
|---|---|
| Create / test / disconnect a connector at the org level | **Owner**, **Admin** |
| View the connectors list | All members |
| Attach a source to a container | Anyone with **edit** access to that container |
| Detach a source / sync a source | Anyone with **edit** access to that container |

## Step 1. Connect at the org level

1. Open **Organization → Connectors** in the Wire dashboard.
2. Click **Add connector** and pick **Amazon S3 / S3-compatible**.
3. Fill in:
   - **Endpoint** (optional, defaults to AWS S3): the S3 endpoint host, e.g. `s3.amazonaws.com`, `<account>.r2.cloudflarestorage.com`, your MinIO host.
   - **Region** (optional, defaults to `us-east-1`): the bucket's region.
   - **Access key ID** + **Secret access key**: an IAM user or service account with read access to the buckets you want Wire to see.
   - **Session token** (optional): for STS / temporary credentials.
   - **Test bucket**: a bucket name Wire will try to read so we can validate the credentials before storing them. This is required, so we don't store credentials we can't actually use.
4. Click **Connect**. Wire validates the credentials against the test bucket, encrypts them, and stores them.

If validation fails, the form shows why (`invalid credentials`, `no access to test bucket`, `bucket not found`, etc.) without storing anything.

### What Wire needs

The minimum permission is **read** on the buckets and prefixes you want to sync from. Wire never writes back to your bucket, never deletes from it, and never lists buckets outside the prefixes you explicitly attach.

A typical IAM policy:

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["s3:GetObject"],
      "Resource": "arn:aws:s3:::your-bucket/your-prefix/*"
    },
    {
      "Effect": "Allow",
      "Action": ["s3:ListBucket"],
      "Resource": "arn:aws:s3:::your-bucket",
      "Condition": {
        "StringLike": { "s3:prefix": ["your-prefix/*"] }
      }
    }
  ]
}
```

## Step 2. Attach a bucket to a container

1. Open the container you want to feed.
2. Go to the **Sources** tab.
3. In the **Amazon S3** row, click **Setup** (or **Edit** if you already have one attached).
4. Click **Add bucket**, pick the connector you just made, and fill in:
   - **Bucket**: the bucket name.
   - **Prefix** (optional): limit the source to a folder, e.g. `reports/2026/`. Leave empty to sync the whole bucket.
5. Click **Add**. Wire kicks off a backfill.

The source row shows its state with a pill:

| Pill | Meaning |
|---|---|
| **Pending** | Queued, backfill hasn't started yet |
| **Syncing** | Backfill is running, files are landing |
| **Synced** | Caught up with the bucket |
| **Error** | Something went wrong, hover the row for details |

Files appear in the container's **Files** list as they finish processing.

## Keeping things in sync

After the initial backfill, Wire checks for changes on a regular cadence and pulls in anything new, updated, or removed. You don't have to do anything.

If you've just made a change at the source and want to pull it immediately, hit **Sync now** on the source row. That fires off an incremental sync right away. If a sync is already in flight, the button waits for it to finish rather than firing a parallel one.

### What an incremental sync does

- New files in the prefix get pulled in and processed
- Changed files (different etag at the source) replace the old version in the container
- Files that disappear from the prefix get removed from the container

Files in the container that you didn't put there (uploads, agent writes, other connectors) are never touched.

## Detaching a source

Go back to the **Sources** tab, hit **Edit**, then the trash icon on the bucket row. Wire archives the source, then cleans up every file it ever pulled from that bucket out of the container, in the background. This includes entries, embeddings, and the stored copies of the files. **Your bucket is never touched.** Detaching only removes Wire's mirror.

If you want to pause syncing without losing the files Wire already pulled, that's not currently supported. Detach removes everything; re-attach starts a fresh backfill.

## Disconnecting a connector

At the org level (**Organization → Connectors → Disconnect**), revoking the connector cascades through every container it ever fed. Each affected container has its synced files removed in the background, exactly as if you'd detached the source from each container individually.

Re-connecting later with the same credentials is fine, but existing sources need to be reattached.

## Costs

Writes and syncs through S3 are **free**. There's no per-file or per-sync cost; pulling 10,000 files in a backfill is the same price as uploading them by hand. Once files are in the container, the usual container charges apply to anything you do with them.

## Privacy and security

- Credentials are encrypted at rest with a key Wire never exposes to its own application code.
- Every read of those credentials is logged. Wire's compliance posture treats them as the most sensitive thing in the system.
- Wire only fetches objects under the prefix you explicitly attach. It doesn't list other buckets, list the bucket root, or read outside the configured prefix.
- The bucket name, prefix, region, and last-checked timestamp are visible to anyone with read access to the connector at the org level. The credentials themselves never come back out.

## Troubleshooting

**Backfill stuck in Pending or Syncing**
Hit **Sync now**. If it stays stuck, check the **Re-test** button on the connector at the org level. If the credentials no longer work (rotated, expired STS, IAM policy changed), the connector's status flips to **invalid credentials** and you need to reconnect.

**A file's contents at the source changed but the container still shows the old version**
Files are matched by their etag. If your source updated the file in a way that doesn't change the etag (rare, but some providers behave differently here), Wire won't see it as changed. Hit **Sync now** and check, or as a last resort, delete and re-upload the file at the source.

**Sync is missing files**
Wire scans up to 100,000 objects per sync run. If a single prefix is bigger than that, split it into narrower prefixes and attach each one as a separate source.

**A new connector won't validate**
The most common cause is the test bucket being unreachable from the credentials given (typo in name, bucket in a different region than configured, or the IAM user lacks `s3:ListBucket` on it). Pick a bucket the credentials can definitely list.