> ## Documentation Index
> Fetch the complete documentation index at: https://api-tools.memories.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Download by Bucket and Blob

> Download a stored video/keyframe/image directly by its GCS bucket and blob path, returned by other Visual Search endpoints.

<Info>
  **Product**: Visual Search
  **Use case**: Stream a stored resource directly from GCS using the `bucket` and `blob` pair returned by search / list / metadata endpoints
  **Host**: `https://api.memories.ai/serve/api/v1`
  **Auth**: `Authorization: sk-mavi-...` (no `Bearer` prefix)
</Info>

Download any stored resource by the `bucket` + `blob` pair returned in the response of other Visual Search endpoints (search results, `list_videos`, `get_metadata`, transcription, etc.). The response body is a **binary file stream**; errors are returned as JSON.

This is functionally equivalent to [Download Video](/visual-search/download-video) but addresses the resource by its storage location directly, so it also works for keyframe images and any other resource whose `bucket` / `blob` is exposed by the API — not just the video file behind a `video_no`.

<Note>
  **Pricing:** Billed by bytes downloaded (\$0.12 / GB), same model as [Download Asset](/visual-intelligence/base/download).
</Note>

## Prerequisites

* You have [created a memories.ai API key](/visual-search/create-your-key).
* You have a `bucket` / `blob` pair from another endpoint — common sources:
  * `data[].video_bucket` / `data[].video_blob` and `data[].keyframe_bucket` / `data[].keyframe_blob` from [Search by Text](/visual-search/search-by-text), [Search Public — by Text](/visual-search/search-public-by-text), [Search by Image](/visual-search/search-by-image), [Search by Transcript](/visual-search/search-by-transcript), etc.
  * `data.bucket` / `data.blob` from [Get Metadata](/visual-search/get-metadata), [List Videos](/visual-search/list-videos), [Get Task Status](/visual-search/get-task-status).
  * `data.video_bucket` / `data.video_blob` from [Get Audio Transcription](/visual-search/audio-transcription) and [Get Video Transcription](/visual-search/video-transcription).

## Endpoint

```
GET /serve/api/v1/download?bucket=<bucket>&blob=<blob>
```

## Authentication

```
Authorization: sk-mavi-...
```

## Query Parameters

<ParamField query="bucket" type="string" required>
  GCS bucket name carrying the resource (e.g. the `bucket` / `video_bucket` / `keyframe_bucket` from a previous response).
</ParamField>

<ParamField query="blob" type="string" required>
  GCS blob (object) path inside `bucket`. URL-encode any reserved characters (slashes inside the blob path are allowed and should not be encoded as `%2F`).
</ParamField>

## Code Examples

<CodeGroup>
  ```python Python theme={null}
  import requests

  url = "https://api.memories.ai/serve/api/v1/download"
  params = {
      "bucket": "mavi-resource",
      "blob": "VI625239098370850816.mp4",
  }
  headers = {"Authorization": "sk-mavi-..."}

  resp = requests.get(url, params=params, headers=headers, stream=True)
  content_type = resp.headers.get("Content-Type", "")

  if "application/json" in content_type.lower():
      # Error path — the server returned a JSON error payload
      print(resp.json())
  else:
      with open("video.mp4", "wb") as f:
          for chunk in resp.iter_content(chunk_size=8192):
              if chunk:
                  f.write(chunk)
      print("Download complete")
  ```

  ```bash cURL theme={null}
  curl --request GET \
    --url 'https://api.memories.ai/serve/api/v1/download?bucket=mavi-resource&blob=VI625239098370850816.mp4' \
    --header 'Authorization: sk-mavi-...' \
    --output video.mp4
  ```

  ```javascript fetch theme={null}
  const url = new URL("https://api.memories.ai/serve/api/v1/download");
  url.searchParams.set("bucket", "mavi-resource");
  url.searchParams.set("blob", "VI625239098370850816.mp4");

  const response = await fetch(url, {
    headers: { Authorization: "sk-mavi-..." },
  });

  if (!response.ok || (response.headers.get("Content-Type") ?? "").includes("application/json")) {
    console.error(await response.json());
  } else {
    const blob = await response.blob();
    const a = Object.assign(document.createElement("a"), {
      href: URL.createObjectURL(blob),
      download: "video.mp4",
    });
    a.click();
    URL.revokeObjectURL(a.href);
  }
  ```
</CodeGroup>

## Successful Response

On success, the server streams the file as binary content:

* **Status:** 200
* **Content-Type:** typically `application/octet-stream` or the original MIME type
* **Content-Disposition:** `attachment; filename="..."` — the original filename
* **Body:** binary content of the resource

<Note>
  The download uses HTTP chunked transfer encoding, so **`Content-Length` is often absent**. Don't rely on it for progress bars — iterate over chunks instead.
</Note>

## Error Response (JSON)

Errors are returned as JSON instead of a binary stream. Branch on `Content-Type` rather than parsing message strings.

Missing `bucket`:

```json theme={null}
{
    "code": "0001",
    "msg": "Bucket cannot be null or empty",
    "data": null,
    "success": false,
    "failed": true
}
```

Missing `blob`:

```json theme={null}
{
    "code": "0001",
    "msg": "Blob cannot be null or empty",
    "data": null,
    "success": false,
    "failed": true
}
```

Object not present in the bucket (wrong values, or resource already deleted):

```json theme={null}
{
    "code": "0001",
    "msg": "Resource does not exist",
    "data": null,
    "success": false,
    "failed": true
}
```

Out of balance (download is billed by bytes; if your account has insufficient balance the request is rejected before the stream starts):

```json theme={null}
{
    "code": "0001",
    "msg": "Insufficient balance to download the resource. Please recharge and try again.",
    "data": null,
    "success": false,
    "failed": true
}
```

## Notes & Limits

* **Rate limiting**: shares the per-account download rate limit with [Download Video](/visual-search/download-video). Exceeding the limit returns *"Request has exceeded the limit."* — see [Rate limits](/visual-search/rate-limits).
* **Billing**: charged by bytes downloaded, not by call count. The deduction happens before the stream is written; you will not be charged for `Resource does not exist` errors.
* **`bucket` / `blob` lifetime**: the values can become invalid if the underlying video is deleted via [Delete Video](/visual-search/delete-video). Always be ready to handle the `Resource does not exist` error.
* **Use the right pair**: when downloading a clip's matched keyframe, use `keyframe_bucket` + `keyframe_blob`; when downloading the full video, use `video_bucket` + `video_blob` (or `bucket` + `blob` from `get_metadata` / `list_videos`).

## Related Endpoints

* [Download Video](/visual-search/download-video) — POST-by-`video_no` equivalent. Use this when you already have a `video_no` and want to skip resolving its `bucket` / `blob` first.
* [Download Asset](/visual-intelligence/base/download) — Visual Intelligence v2 equivalent, by `asset_id`.
