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

# Blob SDK: migrating from v3 to v4

> What changed in @squarecloud/blob 4.0.0: a safer retry policy, fewer default retries, an optional SavedRule.active_from and a consistent one-id batch delete.

Version 4.0.0 changes **how the SDK retries**, so that it only repeats what is safe to repeat. **No method, option or export was renamed or removed.**

## Requirements

Unchanged: **Node.js 20** or newer, or a browser; ESM and CommonJS.

## Breaking changes summary

| v3.x | v4.x |
| - | - |
| `429` retried | **Never retried** (except `TOO_MANY_CONCURRENT_CHUNKS` on a multipart part) |
| Writes retried on network errors and `5xx` | Writes get **a single attempt** |
| Retried on `500` and `503` only | Retried on **any `5xx`** (reads and multipart parts) |
| `maxRetries` default `5` | Default **`2`** |
| Backoff capped at 30 s | Capped at **8 s**: `min(8 s, 500 ms · 2^n) · U(0.5, 1)` |
| `SavedRule.active_from: string` | `active_from?: string` (**optional**) |
| `delete([id])` with one id throws `PREFIX_NOT_ALLOWED` | Reports it in `failed` |
| `RATE_LIMIT` in `BlobErrorCode` | **Deprecated** (still in the type) |

## `429` is no longer retried

`RATE_LIMITED` covers both a per-route window and an account or IP block that can last about 30 minutes, so the SDK no longer retries it. This includes the simple upload limit and `TOO_MANY_CONCURRENT_UPLOADS`. Handle it yourself, and wait before trying again:

```typescript theme={"system"}
import { SquareCloudBlobError } from "@squarecloud/blob";

try {
    await blob.put(file, { name: "report" });
} catch (error) {
    if (error instanceof SquareCloudBlobError && error.code === "RATE_LIMITED") {
        // back off: queue the job for later instead of retrying right away
    }
    throw error;
}
```

The one exception is `TOO_MANY_CONCURRENT_CHUNKS` on a multipart part: the server refuses the part before reading it, so the SDK sends it again within `maxRetries`.

## Writes get a single attempt

Network errors and `5xx` are now retried only on `GET` calls and on multipart upload parts. These calls get **one attempt**:

* simple `put()`, and starting, completing and aborting a multipart upload;
* `update()`, `copy()`, `move()`, `delete()`;
* `rules.set()`, `uploadTokens.create()`, `shares.create()`, `shares.revoke()`.

Retry a write yourself only when repeating it is safe for you, for example a `put()` to the same name with `overwrite: true`. See [Retrying writes yourself](/en/sdks/blob/errors#retrying-writes-yourself).

## Fewer retries, shorter backoff

`maxRetries` now defaults to `2` (was `5`), and the backoff is capped at 8 seconds (was 30). To keep the old budget on reads and multipart parts:

```typescript theme={"system"}
const blob = new SquareCloudBlob(process.env.SQUARECLOUD_API_KEY, { maxRetries: 5 });
```

This does not bring back retries on `429` or on writes.

## `SavedRule.active_from` is optional

The API only sends `active_from` for rules with `delete_after_days`. In TypeScript, handle `undefined`:

```typescript theme={"system"}
const rules = await blob.rules.get();

for (const rule of rules) {
    if (rule.active_from) {
        console.log(`${rule.prefix} starts deleting at ${rule.active_from}`);
    }
}
```

## `delete([id])` with a single id

A batch with one id now reports `PREFIX_NOT_ALLOWED` in `failed`, like any other batch, instead of throwing:

```typescript theme={"system"}
// v3: threw SquareCloudBlobError (PREFIX_NOT_ALLOWED)
// v4:
const { failed } = await blob.delete([id]);
// failed: [{ id, code: "PREFIX_NOT_ALLOWED" }]
```

`delete(id)` with a plain string still throws.

## `RATE_LIMIT` is deprecated

The service no longer sends `RATE_LIMIT`: the account or IP block is `RATE_LIMITED`. The old code stays in `BlobErrorCode` so existing comparisons still compile; switch them to `RATE_LIMITED`. `DUPLICATE_RULE_PREFIX` was added.

## Fixes

* A response body cut off mid-read is now a **network error**: it is retried on `GET` calls and multipart parts, and otherwise the original `fetch` error is thrown (it used to be `UNKNOWN_ERROR`).
* A failed multipart upload now **waits for the parts still in flight** before aborting, so no part lands after the abort and no request outlives `put()`.

## Checklist

<Steps>
  <Step title="Handle 429 yourself">
    Catch `RATE_LIMITED` and back off; the SDK no longer retries it.
  </Step>

  <Step title="Review writes">
    Add your own retry only to writes that are safe to repeat.
  </Step>

  <Step title="Pick a retry budget">
    Pass `{ maxRetries: 5 }` if you relied on the old number of attempts on reads and multipart parts.
  </Step>

  <Step title="Update types">
    Handle `active_from` being `undefined`, and replace `RATE_LIMIT` with `RATE_LIMITED`.
  </Step>
</Steps>

## Next steps

<CardGroup cols={2}>
  <Card title="Client" icon="plug" href="/en/sdks/blob/client">
    Install the SDK and create a client.
  </Card>

  <Card title="Errors" icon="triangle-exclamation" href="/en/sdks/blob/errors">
    The retry policy in detail.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.