> ## 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 上传令牌

> 通过 POST /v1/upload-tokens 签发短期上传令牌，让浏览器无需你的 API key 即可直接上传到 Blob Storage。

<ParamField header="Authorization" type="string" placeholder="API Key" required>
  你账户的 API 密钥。你可以在[账户设置](https://squarecloud.app/zh/account/security)中找到它。
</ParamField>

上传令牌让你的**服务器**签发一个短期令牌，供**浏览器**或移动应用直接上传到 Blob Storage。文件永远不会经过你的服务器，你的 API key 也永远不会离开服务器。需要 `blob:write` scope 和付费计划。

该令牌放在[对象上传](/zh/blob-reference/endpoint/post)或[分块上传](/zh/blob-reference/endpoint/chunked-init)路由的 `Authorization` 请求头中，在其他任何地方都无法使用（`403 UPLOAD_TOKEN_NOT_ALLOWED`）。签发时设置的所有内容都会被**锁定**：浏览器无法更改名称、前缀、可见性、过期时间或元数据，也无法发送更大的文件或使用其他文件类型。

* 每次上传消耗一次使用次数：一次对象上传调用，或开启一次分块上传（其分块和完成操作不会额外消耗）。
* 未指定 `name` 时，由浏览器选择名称，且名称始终带有安全哈希，因此泄露的令牌永远无法替换你已有的文件。
* 当使用次数用尽（`401 UPLOAD_TOKEN_USED`）、令牌过期或签发它的 API key 被撤销（`401 ACCESS_DENIED`）时，令牌即失效。

<ParamField body="name" type="string">
  锁定文件名。未指定时，由浏览器在查询参数中发送 `name`。
</ParamField>

<ParamField body="prefix" type="string">
  锁定前缀。发送了不同前缀的浏览器会收到 `403 PREFIX_NOT_ALLOWED`。
</ParamField>

<ParamField body="private" type="boolean">
  锁定可见性。
</ParamField>

<ParamField body="security_hash" type="boolean">
  仅与 `name` 一起使用：`false` 允许上传替换该名称的文件。否则名称始终带有哈希。
</ParamField>

<ParamField body="expire" type="string | null">
  锁定过期时间（`30d`、`6h`），`null` 表示不过期。7 天以内需要 Enterprise。
</ParamField>

<ParamField body="max_size" type="number">
  最大文件大小（字节），范围为 512 到 10737418240（10 GiB）。
</ParamField>

<ParamField body="allowed_extensions" type="string[]">
  可接受的扩展名，1 到 20 个，小写且不带点（`png`、`tar.gz`）。
</ParamField>

<ParamField body="metadata" type="object">
  添加到通过该令牌上传的每个文件上的元数据。仅限 Pro 和 Enterprise。
</ParamField>

<ParamField body="expires_in" type="number" default="900">
  令牌的有效时长，范围为 60 到 3600 秒。
</ParamField>

<ParamField body="max_uses" type="number" default="1">
  令牌允许的上传次数，范围为 1 到 100。
</ParamField>

### 速率限制

<Note>每分钟 120 个令牌（`RATE_LIMITED`，429）。签发令牌不会存储任何内容，因此为每个终端用户的每次上传签发一个令牌完全没有问题。</Note>

### 响应

<ResponseField name="status" type="string">
  成功为 "success"，否则为 "error"。
</ResponseField>

<ResponseField name="response" type="object">
  <Expandable title="展开对象">
    <ResponseField name="token" type="string">
      上传令牌（`squp_...`）。将它交给浏览器。
    </ResponseField>

    <ResponseField name="expires_at" type="ISO 8601">
      令牌的过期时间。
    </ResponseField>

    <ResponseField name="max_uses" type="number">
      令牌允许的上传次数。
    </ResponseField>
  </Expandable>
</ResponseField>

<RequestExample>
  ```javascript 服务器 (Node.js) theme={null}
  // Your backend: mint a token for the signed-in user and return it to the page.
  const res = await fetch('https://blob.squarecloud.app/v1/upload-tokens', {
    method: 'POST',
    headers: {
      Authorization: process.env.SQUARE_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      prefix: `avatars/${userId}`,
      allowed_extensions: ['png', 'jpg', 'webp'],
      max_size: 2 * 1024 * 1024,
      expires_in: 300,
    }),
  });
  const { response } = await res.json();
  // send response.token to the browser
  ```

  ```javascript 浏览器 theme={null}
  const form = new FormData();
  form.append('file', input.files[0]);

  const res = await fetch('https://blob.squarecloud.app/v1/objects?name=avatar', {
    method: 'POST',
    headers: { Authorization: token },
    body: form,
  });
  const { response } = await res.json();
  // response.url is the public URL of the new file
  ```

  ```bash cURL theme={null}
  curl --request POST \
    --url 'https://blob.squarecloud.app/v1/upload-tokens' \
    --header 'Authorization: YOUR_API_KEY' \
    --header 'Content-Type: application/json' \
    --data '{ "prefix": "avatars", "allowed_extensions": ["png", "jpg"], "max_size": 2097152 }'
  ```
</RequestExample>

<ResponseExample>
  ```json theme={null}
  {
    "status": "success",
    "response": {
      "token": "squp_Tq7xLm2Vb9Rk4Wz1Nc8Hy5Js0Gd3Fp6Ae.Hq3mZ8vT1kLw9xRb",
      "expires_at": "2026-09-25T12:15:00.000Z",
      "max_uses": 1
    }
  }
  ```
</ResponseExample>

### 错误

| 代码                                                                                                                                                                | HTTP | 触发情况                    |
| ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---- | ----------------------- |
| `INVALID_BODY`                                                                                                                                                    | 400  | 请求体不是 JSON 对象。          |
| `INVALID_OBJECT_NAME` / `INVALID_OBJECT_PREFIX` / `INVALID_OBJECT_PRIVATE` / `INVALID_OBJECT_SECURITY_HASH` / `INVALID_OBJECT_EXPIRE` / `INVALID_OBJECT_METADATA` | 400  | 某个锁定的值无效。               |
| `INVALID_MAX_SIZE` / `INVALID_ALLOWED_EXTENSIONS` / `INVALID_EXPIRES_IN` / `INVALID_MAX_USES`                                                                     | 400  | 某个限制超出范围。               |
| `UPLOAD_TOKEN_TOO_LARGE`                                                                                                                                          | 400  | 单个令牌的选项过多。请缩短元数据或扩展名列表。 |
| `PERMISSION_DENIED`                                                                                                                                               | 401  | 账户没有有效的付费计划。            |
| `UPGRADE_REQUIRED`                                                                                                                                                | 403  | 该过期时间或元数据需要更高级别的计划。     |
| `RATE_LIMITED`                                                                                                                                                    | 429  | 一分钟内超过 120 个令牌。         |

浏览器上传时可能出现：`401 UPLOAD_TOKEN_USED`（次数用尽）、`401 ACCESS_DENIED`（已过期或已撤销）、`403 PREFIX_NOT_ALLOWED`（前缀不同）、`400 FILE_TYPE_NOT_ALLOWED`（扩展名不被允许）以及 `413 FILE_TOO_LARGE`（超过 `max_size`）。
