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

# Complete a Direct Blob Upload

> Checks the stored bytes and creates the blob object. Call it after every part is stored. Accepts an upload token.

Finishes a direct upload. The API checks that every part is stored, that the total size matches, that the checksum matches if you sent one, and that the content isn't an executable. Then it creates the object and returns it. Until this call succeeds, the object doesn't exist.

**Scope:** `blob:write`. Accepts an upload token.

**Upload token:** this route also accepts an upload token (`Authorization: Bearer vcup_...`) instead of an API key.

<ParamField path="upload_id" type="string" required>
  The upload ID.
</ParamField>

<ParamField query="workspace_id" type="string">
  The workspace ID, when the blob belongs to a workspace. The call acts on the blob of the workspace owner and needs the `blob:read` workspace permission (`blob:manage` on routes that change data). Leave it out to act on your own blob.
</ParamField>

### Example request

```bash theme={null}
curl -X POST "https://blob.vertracloud.app/v1/uploads/7c1d2e3f-4a5b-4c6d-8e7f-0a1b2c3d4e5f/complete" \
  -H "Authorization: Bearer $VERTRA_API_KEY"
```

### Response

<ResponseField name="response" type="object">
  The created blob object.

  <Expandable title="properties">
    <ResponseField name="id" type="string">Stable ID of the object. It never changes, even when the path, visibility or expiration does.</ResponseField>
    <ResponseField name="path" type="string">Full path inside the account, for example `images/avatars/joao.png`.</ResponseField>
    <ResponseField name="size" type="number">Size in bytes.</ResponseField>
    <ResponseField name="content_type" type="string">MIME type, derived on the server from the file extension.</ResponseField>
    <ResponseField name="etag" type="string">Entity tag of the stored content.</ResponseField>
    <ResponseField name="sha256" type="string | null">SHA-256 of the content in hex, when the upload sent a checksum. `null` otherwise.</ResponseField>
    <ResponseField name="visibility" type="string">`public` (readable through its public URL) or `private` (needs a temporary link or a share).</ResponseField>
    <ResponseField name="url" type="string | null">Public URL. `null` while the object is private.</ResponseField>
    <ResponseField name="cache_control" type="string | null">`immutable`, `max-age=<seconds>` or `no-cache` (`no-cache` needs Enterprise-26 or higher). `null` means the default.</ResponseField>
    <ResponseField name="content_disposition" type="string">`inline` (shown by the browser) or `attachment` (downloaded). HTML, SVG and XML are always `attachment`.</ResponseField>
    <ResponseField name="filename" type="string | null">Name suggested on download. `null` uses the last segment of `path`.</ResponseField>
    <ResponseField name="metadata" type="object">Up to 5 keys, each `[a-z0-9-]{1,64}`, 512 bytes in total. Enterprise-12 or higher; `{}` on other plans.</ResponseField>
    <ResponseField name="expires_at" type="string | null">When the object is deleted, as an ISO 8601 timestamp. `null` means no expiration.</ResponseField>
    <ResponseField name="created_at" type="string">ISO 8601 creation time.</ResponseField>
    <ResponseField name="updated_at" type="string">ISO 8601 time of the last change.</ResponseField>
  </Expandable>
</ResponseField>

<ResponseExample>
  ```json Response theme={null}
  {
    "response": {
      "id": "0b6f7c1e-3d2a-4f5b-9a8c-1d2e3f4a5b6c",
      "path": "images/logo.png",
      "size": 48213,
      "content_type": "image/png",
      "etag": "9b2cf535f27731c974343645a3085d39",
      "sha256": null,
      "visibility": "public",
      "url": "https://blob.vertracloud.dev/k7p2m9x4qa/images/logo.png",
      "cache_control": null,
      "content_disposition": "inline",
      "filename": null,
      "metadata": {},
      "expires_at": null,
      "created_at": "2026-10-14T12:00:00.000Z",
      "updated_at": "2026-10-14T12:00:00.000Z"
    }
  }
  ```
</ResponseExample>

### Error Responses

<ResponseField name="code" type="string">
  The error code indicating the reason for failure. Possible values:

  * `AUTH_TOKEN_MISSING` (`401`): No `Authorization` header was sent.
  * `API_KEY_INVALID` (`401`): The API key doesn't exist, was revoked, or its secret doesn't match.
  * `API_KEY_SCOPE_DENIED` (`403`): The key lacks the scope this route needs (`blob:read` or `blob:write`); `details.required` names it.
  * `WORKSPACE_PERMISSION_DENIED` (`403`): Only with `workspace_id`: you're not a member, or your role lacks the blob permission.
  * `WORKSPACE_NOT_FOUND` (`404`): Only with `workspace_id`: no workspace with that ID, or you're not a member.
  * `BLOB_UPLOAD_TOKEN_INVALID` (`401`): The upload token doesn't exist, has expired or was revoked (only with an upload token).
  * `BLOB_UPLOAD_NOT_FOUND` (`404`): No upload with that ID in this account, or it was already completed or aborted.
  * `BLOB_UPLOAD_EXPIRED` (`410`): The upload's time limit has passed. Start a new upload.
  * `BLOB_UPLOAD_INCOMPLETE` (`409`): Parts are missing, or the stored size differs from the declared `size`.
  * `BLOB_CHECKSUM_MISMATCH` (`400`): The stored content doesn't match the `checksum_sha256` sent at start.
  * `BLOCKED_FILE_TYPE` (`422`): The content is an executable, detected from its bytes.
  * `BLOB_OBJECT_ALREADY_EXISTS` (`409`): `path` was taken by another object while the upload was running, and `overwrite` is not `true`.
</ResponseField>

<ResponseExample>
  ```json Error Response (401) theme={null}
  {
    "code": "AUTH_TOKEN_MISSING"
  }
  ```

  ```json Error Response (403) theme={null}
  {
    "code": "API_KEY_SCOPE_DENIED"
  }
  ```

  ```json Error Response (404) theme={null}
  {
    "code": "WORKSPACE_NOT_FOUND"
  }
  ```

  ```json Error Response (410) theme={null}
  {
    "code": "BLOB_UPLOAD_EXPIRED"
  }
  ```

  ```json Error Response (409) theme={null}
  {
    "code": "BLOB_UPLOAD_INCOMPLETE"
  }
  ```

  ```json Error Response (400) theme={null}
  {
    "code": "BLOB_CHECKSUM_MISMATCH"
  }
  ```

  ```json Error Response (422) theme={null}
  {
    "code": "BLOCKED_FILE_TYPE"
  }
  ```
</ResponseExample>


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