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

# Upload

> Upload files to D3 using presigned URLs.

File uploads are handled via presigned URLs to MinIO. The API exposes two main steps:

* `POST /api/v1/initiate-upload` – create an upload session and receive presigned URLs.
* `POST /api/v1/complete-upload` – finalize the upload and register the file for operations.

**Single-part vs multipart:** For objects **5 GiB (5 × 2³⁰ bytes) or smaller**, you can upload the whole file in **one** presigned `PUT` (`parts: 1`). For **files larger than 5 GiB**, you **must** use **multipart** upload: set `parts` to the number of chunks you will upload (each chunk is uploaded with its own presigned URL), `PUT` each part separately, collect each response’s **`ETag`**, then call **`complete-upload`** with every `(etag, part_number)` pair. This matches S3‑compatible limits: a single `PUT` cannot exceed 5 GiB per object part.

## Initiate upload

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.dragdropdo.com/api/v1/initiate-upload \
    -H "Authorization: Bearer your-api-key" \
    -H "Content-Type: application/json" \
    -d '{
      "file_name": "document.pdf",
      "size": 1234567,
      "mime_type": "application/pdf",
      "parts": 1
    }'
  ```

  ```ts NODE theme={null}
  import { Dragdropdo } from "dragdropdo-sdk";

  const client = new Dragdropdo({
    apiKey: "your-api-key",
    baseURL: "https://api.dragdropdo.com",
  });

  // Initiate upload (handled automatically by uploadFile)
  const uploadResult = await client.uploadFile({
    file: "/path/to/document.pdf",
    fileName: "document.pdf",
    mimeType: "application/pdf",
  });

  console.log("File key:", uploadResult.fileKey);
  console.log("Presigned URLs:", uploadResult.presignedUrls);
  ```

  ```python PYTHON theme={null}
  from dragdropdo_sdk import Dragdropdo, D3ClientConfig

  client = Dragdropdo(
      api_key="your-api-key",
      base_url="https://api.dragdropdo.com"
  )

  # Initiate upload (handled automatically by upload_file)
  upload_result = client.upload_file(
      file="/path/to/document.pdf",
      file_name="document.pdf",
      mime_type="application/pdf"
  )

  print("File key:", upload_result.file_key)
  print("Presigned URLs:", upload_result.presigned_urls)
  ```

  ```php PHP theme={null}
  use DragdropdoSdk\Dragdropdo;

  $client = new Dragdropdo([
      'api_key' => 'your-api-key',
      'base_url' => 'https://api.dragdropdo.com',
  ]);

  // Initiate upload (handled automatically by uploadFile)
  $uploadResult = $client->uploadFile([
      'file' => '/path/to/document.pdf',
      'file_name' => 'document.pdf',
      'mime_type' => 'application/pdf',
  ]);

  echo "File key: " . $uploadResult['file_key'] . "\n";
  echo "Presigned URLs: " . implode(', ', $uploadResult['presigned_urls']) . "\n";
  ```

  ```ruby RUBY theme={null}
  require 'dragdropdo_sdk'

  client = DragdropdoSdk::Dragdropdo.new(
    api_key: 'your-api-key',
    base_url: 'https://api.dragdropdo.com'
  )

  # Initiate upload (handled automatically by upload_file)
  upload_result = client.upload_file(
    file: '/path/to/document.pdf',
    file_name: 'document.pdf',
    mime_type: 'application/pdf'
  )

  puts "File key: #{upload_result[:file_key]}"
  puts "Presigned URLs: #{upload_result[:presigned_urls]}"
  ```

  ```go GO theme={null}
  import "github.com/d3/dragdropdo-sdk-go"

  client, _ := d3.NewDragdropdo(d3.Config{
      APIKey:  "your-api-key",
      BaseURL: "https://api.dragdropdo.com",
  });

  // Initiate upload (handled automatically by UploadFile)
  uploadResult, _ := client.UploadFile(d3.UploadFileOptions{
      File:     "/path/to/document.pdf",
      FileName: "document.pdf",
      MimeType: "application/pdf",
  });

  fmt.Printf("File key: %s\n", uploadResult.FileKey)
  fmt.Printf("Presigned URLs: %v\n", uploadResult.PresignedURLs)
  ```

  ```java JAVA theme={null}
  import com.dragdropdo.sdk.Dragdropdo;
  import com.dragdropdo.sdk.DragdropdoConfig;
  import com.dragdropdo.sdk.models.*;

  Dragdropdo client = new Dragdropdo(
      new DragdropdoConfig("your-api-key")
          .setBaseUrl("https://api.dragdropdo.com")
  );

  // Initiate upload (handled automatically by uploadFile)
  UploadResponse uploadResult = client.uploadFile(
      new UploadFileOptions()
          .setFile("/path/to/document.pdf")
          .setFileName("document.pdf")
          .setMimeType("application/pdf")
  );

  System.out.println("File key: " + uploadResult.getFileKey());
  System.out.println("Presigned URLs: " + uploadResult.getPresignedUrls());
  ```
</CodeGroup>

* `file_name` (**required**) – original file name including extension.
* `size` (**required**) – file size in bytes.
* `mime_type` (**required**) – MIME type (e.g. `application/pdf`).
* `parts` (optional) – number of multipart parts; defaults to `1` when omitted or zero. Use **`1`** for files **≤ 5 GiB**. For **> 5 GiB**, set `parts` to how many pieces you will upload (see [Multipart upload example](#multipart-upload-example-files-larger-than-5-gib)).

**Response (`ExternalUploadResponse`):**

```json theme={null}
{
  "file_key": "file_key_123",
  "object_name": "external/...",
  "upload_id": "upload-id",
  "presigned_urls": [
    "https://minio.example.com/bucket/object?X-Amz-Signature=..."
  ]
}
```

* `file_key` – stable key to reference this file in later operations.
* `object_name` – internal object name in MinIO.
* `upload_id` – underlying multipart upload id (if applicable).
* `presigned_urls` – list of `PUT` URLs to upload file parts.

## Upload file contents

Use the provided `presigned_urls` to upload file data directly to MinIO. For single‑part uploads there will be exactly one URL. For multipart uploads there is **one URL per part**, in order (index `0` → `part_number` **1**, and so on).

### Getting `ETag` values for `complete-upload`

After **each** successful presigned `PUT`:

1. Read the **`ETag`** HTTP response header (not the request body).
2. Remove surrounding **double quotes** if present (S3 and MinIO often return `"abc123..."`).
3. Send that string as `parts[i].etag` with **`part_number`** equal to the 1‑based part index (first URL → `1`, second → `2`, …).

If you omit a part or scramble `part_number` order, `complete-upload` can fail when the storage backend completes the multipart upload.

<CodeGroup>
  ```bash cURL theme={null}
  # 1) Upload file to presigned URL (-i prints response headers; copy ETag for step 2)
  curl -i -X PUT "https://minio.example.com/bucket/object?X-Amz-Signature=..." \
    -H "Content-Type: application/pdf" \
    --data-binary "@document.pdf"

  # 2) Complete upload — use file_key, object_name, upload_id from initiate-upload,
  #    and etag / part_number from the PUT response (strip " quotes from ETag if present)
  curl -X POST https://api.dragdropdo.com/api/v1/complete-upload \
    -H "Authorization: Bearer your-api-key" \
    -H "Content-Type: application/json" \
    -d '{
      "file_key": "file_key_123",
      "object_name": "external/...",
      "upload_id": "upload-id",
      "parts": [
        { "etag": "etag-from-put-response", "part_number": 1 }
      ]
    }'
  ```

  ```ts NODE theme={null}
  import axios from "axios";
  import * as fs from "fs";

  // Upload to presigned URL
  await axios.put(presignedUrl, fs.createReadStream("./document.pdf"), {
    headers: {
      "Content-Type": "application/pdf",
    },
  });

  // Note: The SDK handles this automatically when using uploadFile()
  ```

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

  # Upload to presigned URL
  with open("./document.pdf", "rb") as f:
      requests.put(presigned_url, data=f, headers={"Content-Type": "application/pdf"})

  # Note: The SDK handles this automatically when using upload_file()
  ```

  ```php PHP theme={null}
  // Upload to presigned URL
  $fileContent = file_get_contents("./document.pdf");
  $ch = curl_init($presignedUrl);
  curl_setopt($ch, CURLOPT_PUT, true);
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_HTTPHEADER, ["Content-Type: application/pdf"]);
  curl_setopt($ch, CURLOPT_INFILE, fopen("./document.pdf", "r"));
  curl_exec($ch);
  curl_close($ch);

  // Note: The SDK handles this automatically when using uploadFile()
  ```

  ```ruby RUBY theme={null}
  require 'net/http'
  require 'uri'

  # Upload to presigned URL
  uri = URI(presigned_url)
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true
  request = Net::HTTP::Put.new(uri.request_uri)
  request['Content-Type'] = 'application/pdf'
  request.body = File.read('./document.pdf')
  http.request(request)

  # Note: The SDK handles this automatically when using upload_file()
  ```

  ```go GO theme={null}
  import (
      "net/http"
      "os"
  )

  // Upload to presigned URL
  file, _ := os.Open("./document.pdf")
  defer file.Close()

  req, _ := http.NewRequest("PUT", presignedURL, file)
  req.Header.Set("Content-Type", "application/pdf")
  http.DefaultClient.Do(req)

  // Note: The SDK handles this automatically when using UploadFile()
  ```

  ```java JAVA theme={null}
  import okhttp3.*;
  import java.io.File;

  // Upload to presigned URL
  File file = new File("./document.pdf");
  RequestBody requestBody = RequestBody.create(file, MediaType.parse("application/pdf"));
  Request request = new Request.Builder()
      .url(presignedUrl)
      .put(requestBody)
      .build();
  OkHttpClient client = new OkHttpClient();
  client.newCall(request).execute();

  // Note: The SDK handles this automatically when using uploadFile()
  ```
</CodeGroup>

## Multipart upload example (files larger than 5 GiB)

**Why multipart:** S3‑compatible storage allows at most **5 GiB per single `PUT`**. Objects **larger than 5 GiB** must be split into multiple parts: each part is uploaded with its own presigned URL, and you pass **every** part’s `ETag` into **`complete-upload`**.

**Flow:**

1. Decide how many parts `N` you need (each uploaded part’s body must be **≤ 5 GiB**; size the ranges so the whole file is covered).
2. **`POST /api/v1/initiate-upload`** with **`parts`: `N`**, **`size`**: total file size in bytes, plus `file_name` and `mime_type`. The response includes **`N` entries** in `presigned_urls` (in order: part 1, part 2, …).
3. For each index `i` from `0` to `N - 1`, **`PUT`** the corresponding byte range of your file to `presigned_urls[i]`. After each successful `PUT`, read **`ETag`** from the response headers and strip double quotes (see [Getting `ETag` values for `complete-upload`](#getting-etag-values-for-complete-upload)).
4. **`POST /api/v1/complete-upload`** with **`parts`** listing **all `N` parts**, each with the correct **`part_number`** (`1` … `N`) matching the presigned URL order.

Example: **three** parts — initiate with `"parts": 3`, upload three ranges, then complete with three `{ "etag", "part_number" }` objects.

```bash theme={null}
# 0) Assume archive.tar is > 5 GiB — split into 3 PUTs (ranges are illustrative; use split/dd or your app)
# 1) Initiate — parts must match how many presigned URLs you will use
curl -X POST https://api.dragdropdo.com/api/v1/initiate-upload \
  -H "Authorization: Bearer your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "file_name": "archive.tar",
    "size": 6442450944,
    "mime_type": "application/x-tar",
    "parts": 3
  }'

# Save file_key, object_name, upload_id, presigned_urls[0..2] from JSON.

# 2) PUT each part — capture ETag from each response (-D writes headers to stdout; trim quotes for JSON)
curl -i -X PUT "$PRESIGNED_URL_PART_1" -H "Content-Type: application/x-tar" --data-binary "@part1.bin"
curl -i -X PUT "$PRESIGNED_URL_PART_2" -H "Content-Type: application/x-tar" --data-binary "@part2.bin"
curl -i -X PUT "$PRESIGNED_URL_PART_3" -H "Content-Type: application/x-tar" --data-binary "@part3.bin"

# 3) Complete — one entry per part; part_number matches URL order (1 = first URL)
curl -X POST https://api.dragdropdo.com/api/v1/complete-upload \
  -H "Authorization: Bearer your-api-key" \
  -H "Content-Type: application/json" \
  -d '{
    "file_key": "file_key_123",
    "object_name": "external/...",
    "upload_id": "upload-id",
    "parts": [
      { "etag": "etag-from-put-part-1", "part_number": 1 },
      { "etag": "etag-from-put-part-2", "part_number": 2 },
      { "etag": "etag-from-put-part-3", "part_number": 3 }
    ]
  }'
```

In application code, the pattern is the same as the official SDKs: loop over `presigned_urls`, `PUT` each chunk, push `{ etag: strippedETag, part_number: i + 1 }`, then `POST` the array to **`complete-upload`**.

## Complete upload

`POST /api/v1/complete-upload` finalizes the multipart upload in storage and registers the file so it can be referenced by **`file_key`** in `/api/v1/do` and related APIs. Call it only after every part has been uploaded successfully via the presigned `PUT` URLs.

**Authentication:** same as other Business API routes — `Authorization: Bearer <your-api-key>` and `Content-Type: application/json`.

### Request body

JSON body (`CompleteUploadRequest`):

```json theme={null}
{
  "file_key": "file_key_123",
  "object_name": "external/...",
  "upload_id": "upload-id-from-initiate-response",
  "parts": [
    { "etag": "hex-or-quoted-etag-from-put-response", "part_number": 1 }
  ]
}
```

* `file_key` (**required**) – from `initiate-upload` response.
* `object_name` (**required**) – from `initiate-upload` response.
* `upload_id` (**required**) – from `initiate-upload` response.
* `parts` (**required**) – one entry per uploaded part, in order:
  * `etag` (**required**) – value of the `ETag` header from the corresponding presigned `PUT` response. If the header includes double quotes (common for S3‑compatible storage), strip them before sending.
  * `part_number` (**required**) – 1‑based index matching the part and presigned URL order from initiate upload.

For multipart uploads, include every part; for a single‑part upload, `parts` is a one‑element array.

### Response

On success, HTTP **200** with a JSON body (`CompleteUploadResponse`):

```json theme={null}
{
  "message": "Upload completed successfully. ETag: \"d41d8cd98f00b204e9800998ecf8427e\"",
  "file_key": "file_key_123"
}
```

* `message` – status text from the server (typically includes the stored object ETag after a successful complete). If the upload was already finalized, the message may instead be a short confirmation such as `file upload completed`.
* `file_key` – echoes the key you sent; use this key in operations.

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.dragdropdo.com/api/v1/complete-upload \
    -H "Authorization: Bearer your-api-key" \
    -H "Content-Type: application/json" \
    -d '{
      "file_key": "file_key_123",
      "upload_id": "upload-id",
      "object_name": "external/...",
      "parts": [
        { "etag": "etag-part-1", "part_number": 1 }
      ]
    }'
  ```

  ```ts NODE theme={null}
  import { Dragdropdo } from "dragdropdo-sdk";

  const client = new Dragdropdo({
    apiKey: "your-api-key",
    baseURL: "https://api.dragdropdo.com",
  });

  // uploadFile() runs initiate-upload → presigned PUT(s) → complete-upload for you.
  // For a custom pipeline, POST /api/v1/complete-upload with the JSON body above (e.g. axios.post).
  ```

  ```python PYTHON theme={null}
  from dragdropdo_sdk import Dragdropdo, D3ClientConfig

  client = Dragdropdo(
      api_key="your-api-key",
      base_url="https://api.dragdropdo.com"
  )

  # Note: upload_file() automatically completes the upload
  # All steps are handled automatically by the SDK
  ```

  ```php PHP theme={null}
  use DragdropdoSdk\Dragdropdo;

  $client = new Dragdropdo([
      'api_key' => 'your-api-key',
      'base_url' => 'https://api.dragdropdo.com',
  ]);

  // Note: uploadFile() automatically completes the upload
  // All steps are handled automatically by the SDK
  ```

  ```ruby RUBY theme={null}
  require 'dragdropdo_sdk'

  client = DragdropdoSdk::Dragdropdo.new(
    api_key: 'your-api-key',
    base_url: 'https://api.dragdropdo.com'
  )

  # Note: upload_file() automatically completes the upload
  # All steps are handled automatically by the SDK
  ```

  ```go GO theme={null}
  import "github.com/d3/dragdropdo-sdk-go"

  client, _ := d3.NewDragdropdo(d3.Config{
      APIKey:  "your-api-key",
      BaseURL: "https://api.dragdropdo.com",
  });

  // Note: UploadFile() automatically completes the upload
  // All steps are handled automatically by the SDK
  ```

  ```java JAVA theme={null}
  import com.dragdropdo.sdk.Dragdropdo;
  import com.dragdropdo.sdk.DragdropdoConfig;

  Dragdropdo client = new Dragdropdo(
      new DragdropdoConfig("your-api-key")
          .setBaseUrl("https://api.dragdropdo.com")
  );

  // Note: uploadFile() automatically completes the upload
  // All steps are handled automatically by the SDK
  ```
</CodeGroup>

On success, the server persists metadata and the file becomes available for `/api/v1/do` operations.
