Migrate objects from another provider
Copy a bucket from Amazon S3, Cloudflare R2, Google Cloud Storage, Backblaze B2, Wasabi, DigitalOcean Spaces or another S3 service.
In the dashboard
A migration copies the objects of a bucket at another provider into one of your buckets. We read from the other provider with an access key you give us, copy each object with its content headers and metadata, and keep going in the background until every object is copied. Nothing changes at the other provider. Copies are free; the objects they create are billed as storage.
Before you begin
Section titled Before you begin- Sign in to the dashboard and open the Object Storage service. The service must be
active. - Create the bucket to copy into (Create and delete buckets). It can already hold objects.
- At the other provider, create an access key that can list and read the bucket. Source details by provider says where each provider keeps it. A key that can only read is enough, and is the safest choice.
Source details by provider
Section titled Source details by providerEach provider needs a bucket name and an access key. Some also need a region or an endpoint, which the dialog asks for when you choose the provider.
| Provider | Region | Endpoint | Access key |
|---|---|---|---|
| Amazon S3 | The bucket's region, such as us-east-1. The S3 console shows it beside the bucket. |
Built from the region | An IAM user's access key with s3:ListBucket and s3:GetObject on the bucket. |
| Cloudflare R2 | Not asked for | Your account's S3 API address, https://<account-id>.r2.cloudflarestorage.com, under R2 → Overview in Cloudflare |
An R2 API token with Object Read on the bucket. Use its access key ID and secret access key. |
| Google Cloud Storage | Not asked for | https://storage.googleapis.com |
An HMAC key from Cloud Storage → Settings → Interoperability, for an account that can read the bucket. |
| Backblaze B2 | The part between s3. and .backblazeb2.com in the bucket's endpoint, such as us-west-004 |
Built from the region | An application key that can list and read the bucket. Its key ID is the access key ID. |
| Wasabi | The bucket's region, such as us-east-1 |
Built from the region | An access key whose policy can list and read the bucket. |
| DigitalOcean Spaces | The data centre in the Space's address, such as nyc3 |
Built from the region | A Spaces access key with read access to the Space. |
| Another S3-compatible service | Optional; us-east-1 when left empty |
The service's S3 address, such as https://s3.example.com |
A key that can list and read the bucket. |
An endpoint you type must use HTTPS and be on the public internet. We refuse one that points at a private or reserved address, and one that is Coritan Object Storage itself: to copy between your own buckets, use replication or an S3 client.
Start a migration
Section titled Start a migration- In the dashboard, go to Object Storage, open the service, then the Migrations tab.
- Select New migration….
- Under Copy from, choose the Provider. Fill in the Endpoint or Region when the dialog asks for one (Source details by provider).
- Enter the source Bucket name. To copy part of the bucket, enter a Prefix: only keys that start with it are copied, such as
photos/forphotos/2026/beach.jpg. - Enter the Access key ID and Secret access key.
- Under Copy to, choose the Bucket to copy into. To put the copies under a folder, enter a Prefix, such as
imported/:photos/beach.jpgbecomesimported/photos/beach.jpg. Leave it empty to keep the keys as they are. - Leave Copy every object again off to skip an object your bucket already holds with the same key, size and ETag. Turn it on to copy every object whatever your bucket holds.
- Optionally, select Check source. We list the source with the key and show the first keys we found, so you know the details are right. Nothing is copied yet.
- Select Start migration. We check the source again before we start.
Result
Section titled ResultThe migration appears at the top of the list as Queued and starts within a minute. It runs in the background, so you can close the dashboard. The list refreshes itself while a migration is queued or copying.
| Status | API word | Meaning |
|---|---|---|
| Queued | queued |
Waiting to start. |
| Copying | running |
Copying now. Progress counts the objects done so far, and once we have listed the whole source, how many there are in all. |
| Done | done |
Every object under the prefix was copied or was already there. |
| Done with failures | done |
Finished, but some objects could not be copied. Failed objects and retries. |
| Failed | failed |
The migration stopped. The reason shows under the status. |
| Cancelled | cancelled |
You cancelled it. The objects already copied stay. |
Progress also shows how much data was copied, how many objects were already in your bucket and skipped, and how many failed.
When a migration ends, in any of the last four states, we delete the access key you gave us.
What is copied
Section titled What is copied- Each object's current version, under the destination prefix followed by its source key.
- Its
Content-Type,Cache-Control,Content-Disposition,Content-EncodingandContent-Languageheaders, and its user metadata (x-amz-meta-*). - Its ETag, in most cases. An object the source uploaded in parts is copied in parts of the same size, so S3 clients that compare ETags see the same value at both ends.
These are not copied: earlier versions of an object, object tags, access control lists, storage classes, retention and legal holds, and the bucket's own settings (CORS, lifecycle, public access, notifications). Set those on your bucket yourself.
An object that changes at the source while we copy it fails with "The object changed while we copied it". Retry the migration to copy its new version.
How long a migration takes
Section titled How long a migration takesIt depends mostly on how fast the source sends data, and on how many objects there are: a bucket of many small objects takes longer than one of a few large objects with the same total size. We copy several small objects at once, and an object larger than 16 MB in parts. The Migrations tab shows how far each migration has got.
A service runs at most 3 migrations at a time. To copy several buckets, start one migration for each; the fourth waits until one ends.
Failed objects and retries
Section titled Failed objects and retriesAn object that cannot be copied does not stop the migration: we record it as failed and move on. When the source is busy or does not answer, we stop for now and carry on from the same place a minute later. The reasons an object fails:
- "The source refused to send it (AccessDenied)": the key cannot read that object. Change its permissions at the source.
- "The source keeps it in an archive class that must be restored first": the object is in an archive class, such as S3 Glacier. Restore it at the source.
- "The object changed while we copied it", or "The source no longer has it".
- "Objects over 625 GB are not copied. Copy it with an S3 client".
- "The key is longer than 1024 bytes with the destination prefix": choose a shorter destination prefix.
To see them, open the migration's menu (…) and select Show failed objects. Failed objects lists the most recent first, with the reason and how many times we tried. We keep the last 1000 failures of each migration.
To try again after fixing the cause:
- Select Retry failed objects…, in the migration's menu or on the Failed objects card.
- Enter the Access key ID and Secret access key again. We deleted them when the migration ended. The dialog shows the last four characters of the key the migration used.
- Select Retry failed objects.
The migration is queued again. It copies the failed objects first, then anything it had not reached. When more objects failed than the 1000 we keep, it walks the whole source again and skips what your bucket already holds.
A migration that Failed has Retry migration… instead. It carries on from where it stopped.
A migration stops with Failed when the source refuses the key or the bucket, when the destination bucket is deleted, when the first 100 objects all fail, or when the source has not answered 10 times in a row.
Cancel a migration
Section titled Cancel a migration- Open the migration's menu (…) and select Cancel migration….
- Select Cancel migration to confirm, or Keep copying to leave it running.
We stop copying and delete the access key. The objects already copied stay in your bucket, and nothing changes at the source. A cancelled migration cannot be started again: start a new one, and leave Copy every object again off so it skips what is already copied.
Billing
Section titled BillingMigrations are free: the requests we make to your bucket are not counted as operations. The objects they create count as storage in your service, like any object you upload (How Object Storage is billed). Your old provider may charge for the data and the requests we read from it; check its pricing.
Limits
Section titled Limits| Limit | Value |
|---|---|
| Migrations queued or running in one service | 3 |
| Largest object copied | 625 GB |
| Longest destination key (prefix and source key) | 1024 bytes |
| Source and destination prefix | 1024 bytes each |
| Failed objects kept per migration | 1000 |
| Migrations started, cancelled or retried by one account | 30 an hour |
| Check source by one account | 30 in ten minutes |
With the API
Section titled With the APIEvery request takes your access token as Authorization: Bearer $CORITAN_TOKEN. Find the service's id and the bucket's id with GET /api/v1/client/object-storage and GET /api/v1/client/object-storage/{service_id}/buckets.
Check the source. It answers 422 with the reason when the source refuses, and stores nothing:
curl -X POST https://api.coritan.com/api/v1/client/object-storage/106/migrations/preflight \
-H "Authorization: Bearer $CORITAN_TOKEN" -H "Content-Type: application/json" \
-d '{"provider": "aws", "region": "us-east-1", "source_bucket": "example-media",
"source_prefix": "", "access_key_id": "AKIAEXAMPLE", "secret_access_key": "..."}'
{
"ok": true,
"endpoint": "https://s3.us-east-1.amazonaws.com",
"region": "us-east-1",
"bucket": "example-media",
"prefix": "",
"sample": [{ "key": "index.html", "size": 18204, "last_modified": "2026-09-08T19:41:15+00:00" }],
"more": true
}
provider is one of aws, r2, gcs, b2, wasabi, digitalocean and other. endpoint is required for r2 and other; region for aws, b2, wasabi and digitalocean.
Start the migration with the same fields, plus bucket_id, dest_prefix and overwrite (Copy every object again). It answers 201 with the migration:
curl -X POST https://api.coritan.com/api/v1/client/object-storage/106/migrations \
-H "Authorization: Bearer $CORITAN_TOKEN" -H "Content-Type: application/json" \
-d '{"provider": "aws", "region": "us-east-1", "source_bucket": "example-media",
"access_key_id": "AKIAEXAMPLE", "secret_access_key": "...",
"bucket_id": 501, "dest_prefix": "", "overwrite": false}'
{
"id": 42,
"bucket_id": 501,
"bucket_name": "u7-assets",
"provider": "aws",
"provider_label": "Amazon S3",
"source_bucket": "example-media",
"status": "queued",
"listed_all": false,
"objects_listed": 0,
"objects_copied": 0,
"objects_skipped": 0,
"objects_failed": 0,
"bytes_copied": 0,
"can_retry": false,
"error": null
}
GET .../migrations lists the service's migrations, newest first, with the provider presets and limits. GET .../migrations/{migration_id} answers one. objects_listed is the total only once listed_all is true.
POST .../migrations/{migration_id}/cancel cancels a queued or running migration. POST .../migrations/{migration_id}/retry, with access_key_id and secret_access_key, queues one whose can_retry is true again. GET .../migrations/{migration_id}/failures?limit=100&offset=0 lists its failed objects:
{
"items": [{ "key": "db/2026-01-10/dump-000.sql.gz", "size": 116391936, "error": "The object changed while we copied it",
"attempts": 2, "retry_pending": false, "updated_at": "2026-10-06T18:41:15+00:00" }],
"total": 19,
"kept_max": 1000
}
The API answers 404 for a service, bucket or migration that is not yours, 409 when the service is not active or already runs 3 migrations, and 429 past the rate limits above.
Troubleshooting
Section titled Troubleshooting- "The source did not accept the access key ID" or "The source did not accept the secret access key"
- Copy the key again from the provider. A key with a session token does not work; create a long-lived access key.
- "The key cannot list bucket. Give it permission to list and read the bucket"
- The key exists but has no permission on that bucket. Add list and read permission at the provider.
- "The source has no bucket named bucket"
- Check the bucket name, and for providers with regions, the region.
- "bucket is in another region. Choose the bucket's region and try again"
- Choose the region the provider shows for the bucket.
- "We could not find host. Check the endpoint and the region"
- The endpoint's host name does not exist. Check for a typo, and that the region is a real one for the provider.
- "host points at a private or reserved address. Migrations copy only from public endpoints"
- Use the service's public HTTPS address.
- "This service already has 3 migrations in progress. Wait for one to finish or cancel one"
- Wait for a migration to end, or cancel one, then start the next.
Next steps
Section titled Next stepsAPI operations on this page
| Method | Path | What it does |
|---|---|---|
GET | /api/v1/client/object-storage/{service_id}/migrations | List migrations |
POST | /api/v1/client/object-storage/{service_id}/migrations | Start copying objects from another provider into one of the service's buckets |
POST | /api/v1/client/object-storage/{service_id}/migrations/preflight | Check migration source |
GET | /api/v1/client/object-storage/{service_id}/migrations/{migration_id} | One migration, as the list writes it |
POST | /api/v1/client/object-storage/{service_id}/migrations/{migration_id}/cancel | Stop a queued or running migration and delete its source keys |
POST | /api/v1/client/object-storage/{service_id}/migrations/{migration_id}/retry | Retry migration |
GET | /api/v1/client/object-storage/{service_id}/migrations/{migration_id}/failures | List migration failures |