# Uploading Files

File uploads use a **two-step** process:

1. Call `/upload` endpoint to get a signed URL
2. Upload file directly to the signed URL

Luw\.ai uses a two-step upload process that enables direct-to-CDN uploads for maximum performance. Instead of routing files through our servers, clients get a signed URL and upload directly to our CDN buckets, significantly improving upload speeds and reducing server load.

### Step 1: Get Signed Url and Final Url for Upload

```
POST /v2/upload
```

**Parameters**

| Name     | Type    | Description                                                                                                                                                       |
| -------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| filename | file    | <p><strong>Required</strong></p><p>File name of the file to allocate <strong><code>signed\_url</code></strong> and <strong><code>final\_url</code></strong> .</p> |
| pid      | integer | <p><em>Optional</em></p><p>Persona ID to associate with the upload</p>                                                                                            |

**Curl Example**

```bash
# 1. Get signed URL
curl -X POST https://api.luw.ai/v2/upload \
  -H "Authorization: Bearer LUW_API_TOKEN" \
  -F "filename=image.jpg" \
  -F "pid=123"
```

**Response Example**

```json
{
  "status": true,
  "signed_url": "https://storage.luw.ai/signed-url...",
  "final_url": "https://cdn.luw.ai/luwai/123/image.jpg"
}
```

The **`final_url`** value is the url of the file you've uploaded. After your upload, the url is ready instantly.&#x20;

### Step 2: Use Signed Url to Upload

After you run upload endpoint, you will get **`signed_url`** parameter to upload file directly from client to our servers. You can directly make PUT request with **`signed_url`** to upload your file. And when your upload finish, your app needs to call **`finalize_upload`** endpoint to finalize upload.

```bash
# 2. Upload file
curl -X PUT "<signed_url>" \
  -H "Content-Type: image/jpeg" \
  -H "Cache-Control: max-age=315360000" \
  -H "Expires: Sun, 19 Jul 2030 18:06:32 GMT" \
  --data-binary "@image.jpg"
```

After successful uploads, you can check **response code: 200**

### Step 3: Finalize Upload

After you get response code 200 from upload endpoint, you must call **`finalize_upload`** endpoint to finalize and save uploaded file successfully. You can directly make GET request with **`final_url`** response to the **finalize\_upload** endpoint.

```bash
# 2. Upload file
curl -X PUT "<signed_url>" \
  -H "Content-Type: image/jpeg" \
  -H "Cache-Control: max-age=315360000" \
  -H "Expires: Sun, 19 Jul 2030 18:06:32 GMT" \
  --data-binary "@image.jpg"
```

After successful finalize upload phase, you will get this response:

`{"status" : true}`

&#x20;And then you can use the **`final_url`** comes from /upload endpoint as uploaded file url.

**Full working HTML - Vanilla JavaScript example (try** [**live demo**](https://www.luvi.dev/examples/uploading_files.html) **here):**

```
<!DOCTYPE html>
<html>
<head>
    <title>Luw.ai Upload</title>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
    <input type="text" id="token" placeholder="Your API Token">
    <input type="file" id="file">
    <input type="text" id="pid" placeholder="Persona ID (optional)">
    <button onclick="upload()">Upload</button>
    <div id="progress"></div>
    <div id="result"></div>

    <script>
        async function upload() {
            const fileInput = document.getElementById('file');
            const token = document.getElementById('token').value;
            const pid = document.getElementById('pid').value;
            const progress = document.getElementById('progress');
            const result = document.getElementById('result');
        
            try {
                // Step 1: Get signed URL by sending filename
                const form = new FormData();
                form.append('filename', fileInput.files[0].name);  // Send filename only
                if (pid) form.append('pid', pid);
        
                const {data} = await axios.post('https://api.luw.ai/v2/upload', form, {
                    headers: {'Authorization': `Bearer ${token}`}
                });
        
                progress.textContent = 'Got signed URL, starting upload...';
        
                // Step 2: Upload actual file to signed URL
                await axios.put(data.signed_url, fileInput.files[0], {
                    headers: {
                        'Content-Type': fileInput.files[0].type,
                        'Cache-Control': 'max-age=315360000',
                        'Expires': 'Sun, 19 Jul 2030 18:06:32 GMT'
                    },
                    onUploadProgress: (e) => progress.textContent = `${Math.round(e.loaded/e.total * 100)}%`
                });
                
                // Step 3: Finalize the upload
                await axios.get('https://api.luw.ai/v2/finalize_upload', {
                    params: { url: data.final_url },
                    headers: { 'Authorization': `Bearer ${token}` }
                });
        
                result.innerHTML = `
                    Upload complete!<br>
                    File will be available at: <a href="${data.final_url}">${data.final_url}</a>
                `;
            } catch (err) {
                result.textContent = `Error: ${err.message}`;
            }
        }
    </script>
</body>
</html>
```

### **Supported File Types**

* jpg
* svg
* mp4
* tif/tiff
* png
* jpeg
* webp
* gif
* glb
* gltf
* fbx

### Temporary Uploads vs Permanent Uploads

Please use **`x12tmp-`** prefix for file names for temporary uploads, for example: **`x12tmp-filename.jpg`**. With this prefix, we will keep your files 12 hours on our servers and after it's delete automatically. Other file names are not temporary.

If you don't add prefix, we will save it till your persona deleted. If you don't add Persona ID with **`pid`** parameter to your upload, we automatically add file to your first persona.

So, for long-time storage operations, please provide **`pid`** value, without any prefixes.
