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

# Upload a file to Blob Storage

> Upload a file of up to 100 MB to Blob Storage with POST /v1/objects: public or private, with expiry, cache, metadata and checksum options.

<ParamField header="Authorization" type="string" placeholder="API Key" required>
  The API key for your account. You can find this in your [account settings](https://squarecloud.app/en/account/security).
</ParamField>

Object Post uploads one file and returns its `id` and, for public files, a CDN `url` you can embed or share directly. It backs attachments, generated exports and user-uploaded media for applications hosted on the platform.

A single request accepts files from 512 bytes up to 100 MB. Larger files, up to **10 GiB**, go through the [chunked upload flow](/en/blob-reference/endpoint/chunked-init) or the [S3 gateway](/en/blob-reference/s3-compatibility). Requires the `blob:write` scope, or an [upload token](/en/blob-reference/endpoint/upload-tokens) sent in `Authorization`, and an active plan.

<Note>
  Treat the returned `id` as **opaque**: store it as it comes and send it back to the other routes. It starts with `pub/` or `prv/` and can change when the file changes visibility or expiry. Legacy files, stored before the September 2026 update, keep ids without that prefix, and every route accepts both.
</Note>

## Parameters

<ParamField body="file" type="file" placeholder="logo.png" required>
  Use FormData (`multipart/form-data`), exactly one file per request.<br />Send a real filename: the stored extension comes from it (`reads.fastq.gz` stays `.fastq.gz`).
</ParamField>

<ParamField query="name" type="string" placeholder="logo" required>
  The file name, without extension. 1 to 128 characters: letters, digits, `_`, `.` and `-`, starting with a letter, digit or `_`. It can't contain `..`.
</ParamField>

<ParamField query="prefix" type="string" placeholder="images/2026">
  A folder path for the file, up to 8 segments separated by `/` and 256 characters in total. Each segment follows the same pattern as `name`, up to 64 characters. A trailing `/` is ignored.
</ParamField>

<ParamField query="private" type="boolean" default="false">
  `true` stores the file without a public URL. Read it through [Object Download](/en/blob-reference/endpoint/download), a [share link](/en/blob-reference/endpoint/shares-create) or the [S3 gateway](/en/blob-reference/s3-compatibility). When omitted, the [rule for the prefix](/en/blob-reference/endpoint/settings-put) decides.
</ParamField>

<ParamField query="expire" type="string" placeholder="30d">
  Deletes the file automatically after this time: `30` or `30d` for days, `6h` for hours. From 1 hour to 1825 days (5 years). Expiries under 7 days need the Enterprise plan. When omitted, the rule for the prefix decides.
</ParamField>

<ParamField query="security_hash" type="boolean" default="false">
  `true` adds a random suffix to the name, so the URL can't be guessed and a new upload never replaces an old one. Private files always get it (`false` together with `private=true` is refused).
</ParamField>

<ParamField query="overwrite" type="boolean" default="true">
  Without a security hash, a new upload with the same name and prefix replaces the file. `false` refuses it with `409 OBJECT_ALREADY_EXISTS` instead.
</ParamField>

<ParamField query="disposition" type="string" placeholder="attachment">
  `inline` (open in the browser) or `attachment` (download, with the original file name).
</ParamField>

<ParamField query="auto_download" type="boolean" default="false">
  `true` makes browsers download the file instead of opening it, whatever its type.
</ParamField>

<ParamField query="cache_control" type="string" placeholder="max-age=3600">
  How long the CDN and browsers keep the file: `immutable` (1 year), `max-age=N` with N from 60 to 31536000 seconds, or `no-cache` (every read goes to storage; Enterprise only). Files with a security hash default to `immutable`. The cache never outlives the file's expiry.
</ParamField>

<ParamField query="metadata" type="string" placeholder="{&#x22;order&#x22;:&#x22;8812&#x22;}">
  A JSON object of string values, returned by [Object Info](/en/blob-reference/endpoint/info). Keys use `a-z`, `0-9` and `-` (up to 64 characters). Up to 5 keys and 512 bytes in total. Pro and Enterprise only.
</ParamField>

<ParamField query="checksum_sha256" type="string" placeholder="64 lowercase hex characters">
  The SHA-256 of the file. When it doesn't match, the upload is refused with `CHECKSUM_MISMATCH` and nothing is stored.
</ParamField>

## Rate limits & concurrency

<Note>
  * Every account may have at most **4 uploads in progress** at the same time (`TOO_MANY_CONCURRENT_UPLOADS`, 429).
  * **Hobby** and **Standard** plans are limited to **1 upload per second** (`RATE_LIMITED`, 429). **Pro** and **Enterprise** are exempt.
  * Uploads over the account's included storage are refused with `STORAGE_QUOTA_EXCEEDED`.
</Note>

## File types

Practically any extension is accepted, including formats with no registered MIME type (`.bam`, `.vcf`, `.fasta`, `.parquet`, `.h5`, `.npy` and so on).

* The served `Content-Type` is derived server-side from the extension. Unknown formats are served as `application/octet-stream`.
* Formats a browser renders (`.html`, `.svg`, `.xml` and similar) are always served as downloads.
* Executables and installers are refused with `BLOCKED_FILE_TYPE`: `exe`, `msi`, `dll`, `bat`, `cmd`, `com`, `scr`, `cpl`, `pif`, `hta`, `vbs`, `vbe`, `jse`, `wsf`, `wsh`, `msc`, `reg`, `lnk`, `sys`, `drv`, `ps1`, `apk`, `xpi`.
* A [rule for the prefix](/en/blob-reference/endpoint/settings-put) or an upload token can restrict the accepted extensions and size.

## Response

<ResponseField name="status" type="string">
  "success" if successful, "error" if not.
</ResponseField>

<ResponseField name="response" type="object">
  <Expandable title="Toggle object">
    <ResponseField name="id" type="string">
      The object id: `pub/<user_id>/<path>` or `prv/<user_id>/<path>`. Treat it as opaque.
    </ResponseField>

    <ResponseField name="private" type="boolean">
      Whether the file is private.
    </ResponseField>

    <ResponseField name="url" type="string | null">
      The public CDN URL (`https://blob.squarecloud.dev/pub/...`), or `null` for private files.
    </ResponseField>

    <ResponseField name="expires_at" type="ISO 8601">
      When the file will be deleted. Present only when it expires.
    </ResponseField>

    <ResponseField name="size" type="number">
      The file size, in bytes.
    </ResponseField>

    <ResponseField name="name" type="string">
      The name sent in the request.
    </ResponseField>

    <ResponseField name="prefix" type="string">
      The prefix, when one was sent.
    </ResponseField>

    <ResponseField name="sha256" type="string">
      The SHA-256 of the stored file, in hex.
    </ResponseField>

    <ResponseField name="replaced" type="boolean">
      `true` when the upload replaced an existing file with the same name.
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```bash cURL theme={"system"}
  curl --request POST \
    --url 'https://blob.squarecloud.app/v1/objects?name=logo&prefix=images&security_hash=true' \
    --header 'Authorization: YOUR_API_KEY' \
    --form 'file=@./logo.png'
  ```

  ```javascript JavaScript theme={"system"}
  import fs from 'node:fs';

  const form = new FormData();
  form.append('file', new Blob([fs.readFileSync('./logo.png')]), 'logo.png');

  const params = new URLSearchParams({
    name: 'logo',
    prefix: 'images',
    security_hash: 'true',
  });

  const response = await fetch(`https://blob.squarecloud.app/v1/objects?${params}`, {
    method: 'POST',
    headers: { Authorization: 'YOUR_API_KEY' },
    body: form,
  });
  ```

  ```python Python theme={"system"}
  import requests

  with open('logo.png', 'rb') as f:
      response = requests.post(
          'https://blob.squarecloud.app/v1/objects',
          headers={'Authorization': 'YOUR_API_KEY'},
          params={'name': 'logo', 'prefix': 'images', 'security_hash': 'true'},
          files={'file': ('logo.png', f, 'image/png')},
      )
  ```
</RequestExample>

<ResponseExample>
  ```json Public file theme={"system"}
  {
      "status": "success",
      "response": {
          "id": "pub/3155597145698959364/images/logo_mugws5c0-9f86d081884c7d659a2feaa0c55ad015.png",
          "private": false,
          "url": "https://blob.squarecloud.dev/pub/3155597145698959364/images/logo_mugws5c0-9f86d081884c7d659a2feaa0c55ad015.png",
          "size": 416230,
          "name": "logo",
          "prefix": "images",
          "sha256": "5f70bf18a086007016e948b04aed3b82103a36bea41755b6cddfaf10ace3c6ef",
          "replaced": false
      }
  }
  ```

  ```json Private file theme={"system"}
  {
      "status": "success",
      "response": {
          "id": "prv/3155597145698959364/invoices/2026-09_mugws5c0-1b4f0e9851971998e732078544c96b36.pdf",
          "private": true,
          "url": null,
          "size": 88412,
          "name": "2026-09",
          "prefix": "invoices",
          "sha256": "2c26b46b68ffc68ff99b453c1d30413413422d706483bfa0f98a5e886266e7ae",
          "replaced": false
      }
  }
  ```
</ResponseExample>

## Errors

| Code | HTTP | When |
| - | - | - |
| `INVALID_OBJECT_NAME` / `INVALID_OBJECT_PREFIX` | 400 | `name` or `prefix` doesn't match the pattern. |
| `INVALID_OBJECT_EXPIRE` | 400 | `expire` is not a duration from 1 hour to 1825 days. |
| `INVALID_OBJECT_PRIVATE` / `INVALID_OBJECT_OVERWRITE` / `INVALID_STORAGE_AUTO_DOWNLOAD` | 400 | The flag is not `true` or `false`. |
| `INVALID_OBJECT_SECURITY_HASH` | 400 | Not a boolean, or `false` on a private file. |
| `INVALID_OBJECT_DISPOSITION` / `INVALID_OBJECT_CACHE_CONTROL` / `INVALID_OBJECT_METADATA` / `INVALID_CHECKSUM` | 400 | The value is outside the accepted forms. |
| `INVALID_FILE` / `INVALID_FILE_TYPE` / `BLOCKED_FILE_TYPE` / `FILE_TYPE_NOT_ALLOWED` | 400 | The file part is missing, its extension is malformed or blocked, or the rule or token doesn't allow it. |
| `FILE_TOO_SMALL` | 400 | The file has less than 512 bytes. |
| `CHECKSUM_MISMATCH` | 400 | The file doesn't match `checksum_sha256`. Nothing was stored. |
| `PERMISSION_DENIED` | 401 | The account has no active plan. |
| `UPLOAD_TOKEN_USED` | 401 | The upload token has no uses left. An expired token answers `ACCESS_DENIED`. |
| `STORAGE_QUOTA_EXCEEDED` | 403 | The upload would pass the included storage. |
| `PREFIX_NOT_ALLOWED` | 403 | The upload token is bound to another prefix. |
| `UPGRADE_REQUIRED` | 403 | An option needs a higher plan. The `message` says which. |
| `ACCOUNT_BLOCKED` | 403 | The account is blocked from storing files. |
| `INVALID_CONTENT_TYPE` | 409 | The request is not `multipart/form-data`. |
| `OBJECT_ALREADY_EXISTS` | 409 | The name exists and `overwrite=false`. |
| `FILE_TOO_LARGE` | 413 | Past 100 MB, or past the size allowed by the rule or token. |
| `RATE_LIMITED` / `TOO_MANY_CONCURRENT_UPLOADS` | 429 | 1 upload per second (Hobby and Standard), or 4 in progress. |
| `UPLOAD_FAILED` | 500 | Storage failed. Retry. |
| `PRIVATE_STORAGE_UNAVAILABLE` / `PUBLIC_STORAGE_UNAVAILABLE` | 503 | Storage is temporarily unavailable. Retry. |

See [Errors](/en/blob-reference/errors) for the full list.

## Related

* Blob SDK: [`blob.put()`](/en/sdks/blob/uploads)


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