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

# Upload files

> Upload one or more files with multipart/form-data, understand the response, and handle encryption and quota prerequisites.

Uploads use `multipart/form-data`. The server streams parts through the encryption pipeline as they arrive; it does not buffer the whole file in memory.

## Endpoint

```http theme={null}
POST /files/upload/{bucketId}
POST /files/upload/{bucketId}/{folderId}
```

The folder segment is optional. When present, the file is placed in that folder. The folder must exist and belong to the target bucket.

## Request

| Property | Value |
| - | - |
| Content type | `multipart/form-data` |
| File field name | Not significant; every file part is processed |
| Maximum file parts | 10 |
| Maximum request body | 200 GB |
| Maximum file size | Plan cap, or 100 MB on trial |
| Required metadata | None beyond the path |

```bash theme={null}
curl -X POST "https://api.everabyte.io/api/files/upload/BUCKET_ID" \
  -H "X-Public-Key: $EVB_PUBLIC_KEY" \
  -H "X-Secret-Key: $EVB_SECRET_KEY" \
  -F "file=@./contract.pdf"
```

To upload into a folder:

```bash theme={null}
curl -X POST "https://api.everabyte.io/api/files/upload/BUCKET_ID/FOLDER_ID" \
  -H "X-Public-Key: $EVB_PUBLIC_KEY" \
  -H "X-Secret-Key: $EVB_SECRET_KEY" \
  -F "file=@./contract.pdf"
```

### Multiple files

You can send up to 10 file parts in one request:

```bash theme={null}
curl -X POST "https://api.everabyte.io/api/files/upload/BUCKET_ID" \
  -H "X-Public-Key: $EVB_PUBLIC_KEY" \
  -H "X-Secret-Key: $EVB_SECRET_KEY" \
  -F "file=@./a.pdf" \
  -F "file=@./b.pdf"
```

> [!WARNING]
>
> When a request contains multiple files, the success response describes only the first processed file. The other files may still have been stored. List the bucket or folder after a multi-file upload to discover every resulting `fileId`. Do not assume the response is a complete manifest.

## Response

A successful upload returns `200` with:

```json theme={null}
{
  "status": 200,
  "message": "File uploaded",
  "data": {
    "file": {
      "id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
      "name": "contract",
      "extFile": "pdf"
    },
    "fileId": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
    "effectiveFolderId": null,
    "fileSize": 482133,
    "sizeBytes": 482133,
    "versionId": "3HL4kqtJlcpXroDTDmJ+rmSpXd3dIbrHY+MTRCxf3vjVBH40Nr8X8gdRQBpUMLUo",
    "scheme": "hybrid-v1",
    "encryption": "SSE",
    "isUpdate": false
  }
}
```

| Field | Meaning |
| - | - |
| `file.id` | Same as `fileId` |
| `file.name` | Display name without extension |
| `file.extFile` | Lowercased extension, without the dot |
| `effectiveFolderId` | Folder actually used, or `null` |
| `fileSize`, `sizeBytes` | Stored size in bytes |
| `versionId` | Storage version identifier for this upload |
| `scheme` | Encryption key-derivation scheme label |
| `encryption` | Always `SSE` for this pipeline |
| `isUpdate` | True when this upload created a new version of an existing file |
| `warning` | Present when storage is below the low-capacity threshold |

The `warning` object, when present, has this shape:

```json theme={null}
{
  "warning": {
    "is_low": true,
    "threshold_bytes": 10737418240,
    "remaining_bytes": 9663676416,
    "message": "Storage is running low."
  }
}
```

Values are illustrative.

## Prerequisites

> **Active encryption keys**
>
> The owning user context must have active HPKE keys containing an X25519 public key and an ML-KEM public key. Without them the upload fails with `400` before storage. Generate keys in the app, then retry.

> **Active billing**
>
> Upload is a write operation. Lapsed subscriptions are refused. Trial accounts can upload within trial limits.

> **Bucket scope and capacity**
>
> The key must have `buckets:write` and `files:write`, and must be allowed to target the bucket. The file must fit the remaining storage capacity and the plan's per-file limit.

> **Folder ownership**
>
> If a folder is specified, it must exist and belong to the same bucket. Cross-bucket folder mismatches are rejected.

## Versioning behavior

If a file with the same name and extension already exists in the same folder scope, the upload can become a new version of that file. The response then has `isUpdate: true`.

Do not infer version creation from the filename alone. Persist the returned `fileId` and `versionId`.

## Error behavior during streaming

Validation can happen while the body is still arriving. A rejection may occur after your client has sent megabytes. Handle:

* a non-2xx response even after the request body started,
* connection reset when the server drains the body to deliver the error,
* `413` for trial file-size violations,
* `402` for insufficient storage or billing,
* `400` for missing files, multipart errors, missing encryption keys, or validation failures.

Your client must not assume success until it receives a `fileId`.

## Safe file processing

The API stores the bytes you send. It does not decide whether the content is safe for your application.

> [!WARNING]
>
> Treat every uploaded file as untrusted. Validate type and size before upload, scan untrusted content, and never execute uploaded files.

Recommended controls:

1. Enforce an allowlist of extensions and MIME types in your application.
2. Verify the declared size before starting a large upload.
3. Scan untrusted files after upload, before exposing them to other users.
4. Store the returned `fileId`, not the user-supplied filename, as the authoritative reference.
5. Sanitize filenames for display; do not use them as filesystem paths.
6. If you need integrity verification, compute a hash on your side and compare after download.

There is no documented client-supplied checksum field. Do not send one and assume the server validates it.

## JavaScript example

```javascript theme={null}
import fs from "node:fs";
import FormData from "form-data";

const form = new FormData();
form.append("file", fs.createReadStream("./contract.pdf"));

const response = await fetch(
  `https://api.everabyte.io/api/files/upload/${bucketId}`,
  {
    method: "POST",
    headers: {
      "X-Public-Key": process.env.EVB_PUBLIC_KEY,
      "X-Secret-Key": process.env.EVB_SECRET_KEY,
      ...form.getHeaders(),
    },
    body: form,
  },
);

const body = await response.json();
if (!response.ok) {
  throw new Error(`${response.status} ${body.code ?? body.message}`);
}

console.log(body.data.fileId);
```

## TypeScript example with timeout

```typescript theme={null}
import fs from "node:fs";
import FormData from "form-data";

async function uploadFile(bucketId: string, path: string): Promise<string> {
  const form = new FormData();
  form.append("file", fs.createReadStream(path));

  const controller = new AbortController();
  const timeout = setTimeout(() => controller.abort(), 10 * 60 * 1000);

  try {
    const response = await fetch(
      `https://api.everabyte.io/api/files/upload/${bucketId}`,
      {
        method: "POST",
        headers: {
          "X-Public-Key": process.env.EVB_PUBLIC_KEY!,
          "X-Secret-Key": process.env.EVB_SECRET_KEY!,
          ...form.getHeaders(),
        },
        body: form,
        signal: controller.signal,
      },
    );

    const body = await response.json();
    if (!response.ok) {
      throw new Error(`Upload failed: ${response.status} ${body.code ?? ""}`);
    }
    return body.data.fileId;
  } finally {
    clearTimeout(timeout);
  }
}
```

## Python example

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

with open("contract.pdf", "rb") as handle:
    response = requests.post(
        f"https://api.everabyte.io/api/files/upload/{bucket_id}",
        headers={
            "X-Public-Key": os.environ["EVB_PUBLIC_KEY"],
            "X-Secret-Key": os.environ["EVB_SECRET_KEY"],
        },
        files={"file": ("contract.pdf", handle, "application/pdf")},
        timeout=(30, 600),
    )

response.raise_for_status()
print(response.json()["data"]["fileId"])
```

## PHP example

```php theme={null}
<?php
$client = new GuzzleHttp\Client();

$response = $client->post(
    "https://api.everabyte.io/api/files/upload/{$bucketId}",
    [
        "headers" => [
            "X-Public-Key" => getenv("EVB_PUBLIC_KEY"),
            "X-Secret-Key" => getenv("EVB_SECRET_KEY"),
        ],
        "multipart" => [
            [
                "name" => "file",
                "contents" => fopen("contract.pdf", "r"),
                "filename" => "contract.pdf",
            ],
        ],
        "timeout" => 600,
    ],
);

$body = json_decode($response->getBody()->getContents(), true);
echo $body["data"]["fileId"];
```

## Laravel example

```php theme={null}
<?php
use Illuminate\Support\Facades\Http;

$response = Http::withHeaders([
    'X-Public-Key' => config('services.everabyte.public_key'),
    'X-Secret-Key' => config('services.everabyte.secret_key'),
])
    ->timeout(600)
    ->attach('file', file_get_contents($path), basename($path))
    ->post("https://api.everabyte.io/api/files/upload/{$bucketId}");

$response->throw();
$fileId = $response->json('data.fileId');
```

## Go example

```go theme={null}
package main

import (
	"bytes"
	"fmt"
	"io"
	"mime/multipart"
	"net/http"
	"os"
	"time"
)

func upload(bucketID, path string) error {
	file, err := os.Open(path)
	if err != nil {
		return err
	}
	defer file.Close()

	var body bytes.Buffer
	writer := multipart.NewWriter(&body)
	part, err := writer.CreateFormFile("file", path)
	if err != nil {
		return err
	}
	if _, err := io.Copy(part, file); err != nil {
		return err
	}
	if err := writer.Close(); err != nil {
		return err
	}

	req, err := http.NewRequest(
		http.MethodPost,
		fmt.Sprintf("https://api.everabyte.io/api/files/upload/%s", bucketID),
		&body,
	)
	if err != nil {
		return err
	}
	req.Header.Set("X-Public-Key", os.Getenv("EVB_PUBLIC_KEY"))
	req.Header.Set("X-Secret-Key", os.Getenv("EVB_SECRET_KEY"))
	req.Header.Set("Content-Type", writer.FormDataContentType())

	client := &http.Client{Timeout: 10 * time.Minute}
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode >= 300 {
		return fmt.Errorf("upload failed: %s", resp.Status)
	}
	return nil
}
```

> [!NOTE]
>
> These are examples, not official SDKs. They omit retries, structured error decoding, and secret management for brevity.


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