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

# File Management

> Upload, list, and delete files with user-isolated storage

## Overview

The File Management API allows you to handle file uploads to S3 with automatic user isolation, deduplication, and integrity verification. All files are scoped to individual users, ensuring data privacy and security.

### Key Features

* **Direct S3 Uploads**: Generate presigned URLs for client-side uploads
* **Automatic Deduplication**: MD5-based file detection
* **User Isolation**: Files are automatically scoped to authenticated users
* **Integrity Verification**: MD5 hash validation
* **Flexible MD5 Format**: Accepts both hex and base64 formats

***

## Complete Upload Flow

Here's a complete example of uploading a file:

<Steps>
  <Step title="Calculate MD5 Hash">
    Calculate the MD5 hash of your file. You can use code or an online MD5 generator:

    <Tabs>
      <Tab title="Node.js">
        ```javascript theme={null}
        import crypto from 'crypto';
        import fs from 'fs';

        const fileBuffer = fs.readFileSync('document.pdf');
        const md5Hex = crypto.createHash('md5').update(fileBuffer).digest('hex');
        // or base64: .digest('base64')
        ```
      </Tab>

      <Tab title="Online Tool">
        You can also use online MD5 hash generators. Simply search for "MD5 hash generator" in your browser, upload your file, and copy the generated hash. Both hex and base64 formats are accepted.
      </Tab>
    </Tabs>
  </Step>

  <Step title="Request Presigned URL">
    Call the presigned URL endpoint with file metadata:

    ```javascript theme={null}
    const response = await fetch('https://production.runalloy.com/2025-09/connectors/files/upload/request', {
      method: 'POST',
      headers: {
        'Authorization': 'Bearer YOUR_API_KEY',
        'x-api-version': '2025-09',
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        filename: 'document.pdf',
        mimeType: 'application/pdf',
        md5: md5Hex
      })
    });

    const { presignedUrl, md5, s3Key, type } = (await response.json()).presignedUrl;
    ```
  </Step>

  <Step title="Upload to S3">
    Use the presigned URL to upload directly to S3:

    ```javascript theme={null}
    await fetch(presignedUrl, {
      method: 'PUT',
      headers: {
        'Content-Type': 'application/pdf',
        'Content-MD5': md5 // Use the base64 MD5 from step 2
      },
      body: fileBuffer
    });
    ```
  </Step>

  <Step title="Use the File">
    The file is now stored in S3 and can be referenced by its `s3Key` in your application.

    You can verify your uploaded files using the [List Files endpoint](/reference/connectivity-api/list-user-files).

    ### Using s3Key with Connector Actions

    You can use the `s3Key` as file content in connector actions. For example, uploading to Google Drive:

    ```bash theme={null}
    curl --location 'https://production.runalloy.com/connectors/googleDrive/actions/uploadFile/execute' \
    --header 'x-api-version: 2025-09' \
    --header 'Content-Type: application/json' \
    --header 'Authorization: Bearer {{token}}' \
    --data '{
        "credentialId": "6901cf34fa3f225f1ceaa28c",
        "requestBody": {
            "metadata": {
                "name": "w3img upload",
                "mimeType": "image/jpeg"
            },   
            "s3Key": "651eb6e11d156e0d7a42c76i_w3img.jpg"
        },
        "queryParameters": {
            "uploadType": "multipart"
        }
    }'
    ```
  </Step>
</Steps>

***

## Security

<Warning>
  All files are automatically scoped to the authenticated user. Users cannot access or delete files belonging to other users.
</Warning>

### User Isolation

* List endpoint only returns files for the authenticated user
* Delete endpoint validates file ownership before deletion

### File Validation

* MD5 verification ensures file integrity
* S3 validates Content-MD5 header during upload
* Invalid MD5 results in upload rejection

***

## Best Practices

<CardGroup cols={2}>
  <Card title="Use MD5 for Deduplication" icon="fingerprint">
    Always provide MD5 hashes to avoid uploading duplicate files and verify integrity
  </Card>

  <Card title="Handle Presigned URL Expiry" icon="clock">
    Presigned URLs expire after 1 hour. Generate a new one if needed
  </Card>

  <Card title="Store S3 Keys" icon="database">
    Save the returned `s3Key` in your database for future reference
  </Card>

  <Card title="Check File Existence" icon="magnifying-glass">
    Use the `type` field to detect if a file already exists before uploading
  </Card>
</CardGroup>

***

## Limits

| Feature              | Limit                                       |
| -------------------- | ------------------------------------------- |
| Presigned URL Expiry | 1 hour                                      |
| Maximum File Size    | 5 GB (standard PUT)                         |
| S3 Bucket            | `alloy-user-files`                          |
| File Name Pattern    | `{userId}_{sanitized_filename}.{extension}` |

***

## FAQs

<AccordionGroup>
  <Accordion title="What MD5 format should I use?">
    You can provide MD5 in either **hex** (32 characters) or **base64** (24 characters) format. The system automatically converts hex to base64 if needed.

    Example:

    * Hex: `2942bfabb3d05332b66eb128e0842cff`
    * Base64: `KUK/q7PQUzK2brEo4IQs/w==`
  </Accordion>

  <Accordion title="What happens if I upload the same file twice?">
    The system detects duplicate files using MD5 hashes and returns `type: "existing"`. This helps you avoid re-uploading identical files.
  </Accordion>

  <Accordion title="Can I upload files larger than 5GB?">
    For files larger than 5GB, you'll need to use multipart uploads. Contact support for implementation details.
  </Accordion>

  <Accordion title="How do I calculate MD5 in the browser?">
    Use the browser's native crypto API or libraries like `spark-md5`:

    ```javascript theme={null}
    import SparkMD5 from 'spark-md5';

    const spark = new SparkMD5.ArrayBuffer();
    const fileReader = new FileReader();

    fileReader.onload = (e) => {
      spark.append(e.target.result);
      const md5 = spark.end();
    };

    fileReader.readAsArrayBuffer(file);
    ```
  </Accordion>

  <Accordion title="Are files automatically deleted?">
    No, files persist until explicitly deleted via the delete endpoint. Implement your own cleanup logic as needed.
  </Accordion>
</AccordionGroup>
