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

# Upload File Using Signed URL

> Upload file directly to storage using pre-signed URL

<Info>
  **Product**: Visual Intelligence — Asset Management
  **Use case**: Upload, manage, and download video/image assets used by other Visual Intelligence APIs
  **Host**: `https://mavi-backend.memories.ai/serve/api/v2`
  **Auth**: `Authorization: sk-mavi-...` (no `Bearer` prefix)
</Info>

This is a client-side method to upload files using the signed URL obtained from the `/upload/signed-url` endpoint.

<Note>
  **Pricing:**

  * API calls are free
  * Storage fee: \$0.001/1GB per day
</Note>

<Tip>
  **Quick Start**: Use the "Complete Example" section below to get a signed URL and upload a file in one go. Just replace the file path and run the code!
</Tip>

### Workflow

1. Get a signed URL and `asset_id` from `/upload/signed-url`
2. Use the signed URL to upload your file directly to storage
3. Verify upload completion via `/get-metadata` (poll until `upload_status` is `SUCCESS`)

### Complete Example (Ready to Test)

Here's a complete example that shows how to get a signed URL and use it for upload:

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

  BASE_URL = "https://mavi-backend.memories.ai/serve/api/v2"
  API_KEY = "sk-mavi-..."
  HEADERS = {
      "Authorization": f"{API_KEY}"
  }

  # Step 1: Get signed URL from /upload/signed-url
  def get_signed_url(original_filename: str) -> tuple[str, str]:
      """Get a signed URL and asset_id for file upload"""
      url = f"{BASE_URL}/upload/signed-url"
      data = {"original_filename": original_filename}
      response = requests.post(url, json=data, headers=HEADERS)
      result = response.json()
      print(f"Signed URL response: {result}")
      return result['data']['signed_url'], result['data']['asset_id']

  # Step 2: Upload file using the signed URL
  def upload_by_signed_url(signed_url: str, file_path: str) -> tuple[int, str]:
      """
      Upload file using signed URL
      
      Args:
          signed_url: The signed URL obtained from /upload/signed-url
          file_path: Path to the local file to upload
      
      Returns:
          Tuple of (status_code, response_text)
      """
      with open(file_path, "rb") as f:
          files = {"file": (file_path, f)}
          resp = requests.post(signed_url, files=files)
      
      return resp.status_code, resp.text

  # Complete workflow - ready to test
  if __name__ == "__main__":
      # Replace with your actual file path
      file_path = "path/to/your/video.mp4"
      original_filename = "video.mp4"
      
      # Get signed URL and asset_id
      print("Step 1: Getting signed URL...")
      signed_url, asset_id = get_signed_url(original_filename)
      print(f"Asset ID: {asset_id}")
      print(f"Signed URL: {signed_url[:100]}...")
      
      # Upload file
      print("\nStep 2: Uploading file...")
      status, response = upload_by_signed_url(signed_url, file_path)
      print(f"Upload status: {status}")
      print(f"Upload response: {response}")
      print(f"\nUse asset_id '{asset_id}' for subsequent API calls (e.g., /video/edit, /transcriptions)")
  ```

  ```bash cURL theme={null}
  # Step 1: Get signed URL
  curl --request POST \
    --url https://mavi-backend.memories.ai/serve/api/v2/upload/signed-url \
    --header 'Authorization: sk-mavi-...' \
    --header 'Content-Type: application/json' \
    --data '{
      "original_filename": "video.mp4"
    }'

  # Response will contain signed_url in data.signed_url
  # Step 2: Use the signed_url to upload your file
  # Replace <SIGNED_URL> with the actual signed_url from Step 1
  curl --request POST \
    --url "<SIGNED_URL>" \
    --form 'file=@path/to/your/video.mp4'
  ```
</CodeGroup>

### Code Examples (Using Existing Signed URL)

<CodeGroup>
  ```javascript fetch theme={null}
  // Assuming you have the signed URL
  const signedUrl = "https://storage.example.com/upload?signature=...";
  const fileInput = document.getElementById('fileInput');
  const file = fileInput.files[0];

  const formData = new FormData();
  formData.append('file', file);

  const response = await fetch(signedUrl, {
    method: 'POST',
    body: formData
  });

  if (response.ok) {
    console.log('Upload successful');
  } else {
    console.error('Upload failed:', response.status);
  }
  ```

  ```javascript axios theme={null}
  import axios from 'axios';

  async function uploadBySignedUrl(signedUrl, file) {
    const formData = new FormData();
    formData.append('file', file);

    try {
      const response = await axios.post(signedUrl, formData, {
        headers: {
          'Content-Type': 'multipart/form-data'
        }
      });
      return response.status;
    } catch (error) {
      console.error('Upload error:', error);
      throw error;
    }
  }

  // Usage
  const signedUrl = "https://storage.example.com/upload?signature=...";
  const status = await uploadBySignedUrl(signedUrl, file);
  console.log('Upload status:', status);
  ```

  ```python Python theme={null}
  import requests

  def upload_by_signed_url(signed_url_info, file_path):
      """
      Upload file using signed URL

      Args:
          signed_url_info: String containing the signed URL
          file_path: Path to the local file to upload

      Returns:
          Tuple of (status_code, response_text)
      """
      upload_url = signed_url_info

      with open(file_path, "rb") as f:
          files = {"file": (file_path, f)}
          resp = requests.post(upload_url, files=files)

      return resp.status_code, resp.text

  # Usage example
  signed_url = "https://storage.example.com/upload?signature=..."
  status, response = upload_by_signed_url(signed_url, "path/to/video.mp4")
  print(f"Upload status: {status}")
  ```
</CodeGroup>

### Parameters

| Parameter   | Type      | Required | Description                                           |
| ----------- | --------- | -------- | ----------------------------------------------------- |
| signed\_url | string    | Yes      | The pre-signed URL obtained from `/upload/signed-url` |
| file        | File/Blob | Yes      | The file to upload                                    |

### Response

Returns HTTP status code and response text from the storage service.

**Success Response (200):**

```
Status: 200 OK
File uploaded successfully
```

**Error Response (4xx/5xx):**

```
Status: 400/500
Error message from storage service
```

### Confirming Upload Completion

After uploading via the signed URL, the MAVI backend automatically detects the file. Use the [Get Metadata](/visual-intelligence/base/get-metadata) endpoint with the `asset_id` (returned in Step 1) to verify the upload is complete:

```python theme={null}
# Step 3: Confirm upload is complete
metadata = get_metadata(asset_id)
status = metadata['data']['resource'][0]['upload_status']
print(f"Upload status: {status}")  # Should be "SUCCESS"
```

| Status      | Description                                 |
| ----------- | ------------------------------------------- |
| `WAITING`   | Upload request received, waiting to process |
| `UPLOADING` | File is being uploaded and processed        |
| `SUCCESS`   | Upload completed — asset is ready for use   |
| `FAILED`    | Upload failed — retry the upload            |

<Warning>
  Only assets with `upload_status: "SUCCESS"` can be used in subsequent API calls (transcription, editing, etc.). Poll the [Get Metadata](/visual-intelligence/base/get-metadata) endpoint until the status is `SUCCESS` before proceeding.
</Warning>

### Notes

* This upload happens directly to cloud storage (e.g., GCS)
* No authentication headers needed — the URL contains the signature
* Signed URLs expire after the time specified in `expires_in` (typically 26 hours)
* Make sure to upload the file before the URL expires
* Supports files up to **5 GB** (for files under 500 MB, you can also use the simpler [direct upload](/visual-intelligence/base/upload))
