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

# Presigned Upload: Get S3 Upload URL for a Transfer

> Get presigned R2 URLs for client-side AgentDrop file upload. Bypasses the API server for large transfers; pair with confirm-transfer to flip status to active.

Get presigned URLs for uploading files directly to storage. Use this for browser uploads, large files, or when you want to upload files from the client side without routing them through your backend.

<Note>
  After uploading all files to the presigned URLs, you must call [confirm-transfer](/api-reference/confirm-transfer) to activate the transfer. The transfer stays in `pending` status until confirmed.
</Note>

## Request

### Headers

<ParamField header="Authorization" type="string" required>
  Bearer token. Example: `Bearer agd_live_xxxxxxxxxxxxxxxxxxxx`
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Must be `application/json`
</ParamField>

### Body Parameters

<ParamField body="mode" type="string" required>
  Transfer mode. One of: `agent-to-agent`, `agent-to-human`, `human-to-agent`.
</ParamField>

<ParamField body="sender" type="string" required>
  Identifier for the sending agent or human.
</ParamField>

<ParamField body="recipient" type="string" required>
  Identifier for the intended recipient agent or human.
</ParamField>

<ParamField body="files" type="array" required>
  Array of file descriptor objects. Each object must contain `name`, `size`, and `content_type`. Optionally include `encryption_iv` if the file is client-side encrypted.
</ParamField>

<ParamField body="expires_in" type="string" default="24h">
  How long the transfer stays active. Examples: `1h`, `12h`, `24h`, `7d`, `30d`.
</ParamField>

<ParamField body="max_downloads" type="integer" default="10">
  Maximum number of times the transfer can be downloaded.
</ParamField>

<ParamField body="message" type="string">
  Optional message to attach to the transfer.
</ParamField>

<ParamField body="metadata" type="object">
  Optional metadata object for custom key-value pairs.
</ParamField>

<ParamField body="is_encrypted" type="boolean" default="false">
  Whether the files are client-side encrypted before upload.
</ParamField>

<ParamField body="encrypted_key" type="string">
  Base64-encoded encrypted symmetric key. Required when `is_encrypted` is `true`.
</ParamField>

<ParamField body="encryption_algorithm" type="string" default="AES-256-GCM">
  Encryption algorithm used. Currently only `AES-256-GCM` is supported.
</ParamField>

<ParamField body="auto_delete" type="boolean" default="false">
  Whether to automatically delete the transfer after it expires.
</ParamField>

## Response

<ResponseField name="id" type="string">
  The transfer ID. Use this when calling the confirm endpoint.
</ResponseField>

<ResponseField name="upload_urls" type="array">
  Array of presigned upload objects, one per file.
</ResponseField>

<ResponseField name="upload_urls[].file_id" type="string">
  Unique ID for this file within the transfer.
</ResponseField>

<ResponseField name="upload_urls[].file_name" type="string">
  The file name you specified.
</ResponseField>

<ResponseField name="upload_urls[].upload_url" type="string">
  Presigned URL to upload the file to.
</ResponseField>

<ResponseField name="upload_urls[].method" type="string">
  HTTP method to use. Typically `PUT`.
</ResponseField>

<ResponseField name="upload_urls[].headers" type="object">
  Headers to include in the upload request.
</ResponseField>

<ResponseField name="confirm_url" type="string">
  URL to call after all files are uploaded.
</ResponseField>

<ResponseField name="expires_at" type="string">
  ISO 8601 timestamp when the presigned URLs expire.
</ResponseField>

## Examples

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.agent-drop.com/v1/transfers/presigned \
    -H "Authorization: Bearer agd_live_xxxxxxxxxxxxxxxxxxxx" \
    -H "Content-Type: application/json" \
    -d '{
      "mode": "agent-to-agent",
      "sender": "data-pipeline",
      "recipient": "analysis-agent",
      "files": [
        { "name": "report.pdf", "size": 1048576, "content_type": "application/pdf" },
        { "name": "data.csv", "size": 245760, "content_type": "text/csv" }
      ],
      "expires_in": "12h",
      "max_downloads": 3,
      "message": "Weekly metrics"
    }'
  ```

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

  response = requests.post(
      "https://api.agent-drop.com/v1/transfers/presigned",
      headers={
          "Authorization": "Bearer agd_live_xxxxxxxxxxxxxxxxxxxx",
          "Content-Type": "application/json",
      },
      json={
          "mode": "agent-to-agent",
          "sender": "data-pipeline",
          "recipient": "analysis-agent",
          "files": [
              {"name": "report.pdf", "size": 1048576, "content_type": "application/pdf"},
              {"name": "data.csv", "size": 245760, "content_type": "text/csv"},
          ],
          "expires_in": "12h",
          "max_downloads": 3,
          "message": "Weekly metrics",
      },
  )

  result = response.json()

  # Upload each file to its presigned URL
  for url_info in result["upload_urls"]:
      with open(url_info["file_name"], "rb") as f:
          requests.put(url_info["upload_url"], data=f, headers=url_info["headers"])

  # Confirm the transfer
  requests.post(result["confirm_url"], headers={"Authorization": "Bearer agd_live_xxxxxxxxxxxxxxxxxxxx"})
  ```

  ```javascript Node.js theme={null}
  const response = await fetch(
    "https://api.agent-drop.com/v1/transfers/presigned",
    {
      method: "POST",
      headers: {
        Authorization: "Bearer agd_live_xxxxxxxxxxxxxxxxxxxx",
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        mode: "agent-to-agent",
        sender: "data-pipeline",
        recipient: "analysis-agent",
        files: [
          { name: "report.pdf", size: 1048576, content_type: "application/pdf" },
          { name: "data.csv", size: 245760, content_type: "text/csv" },
        ],
        expires_in: "12h",
        max_downloads: 3,
        message: "Weekly metrics",
      }),
    }
  );

  const result = await response.json();

  // Upload each file to its presigned URL
  for (const urlInfo of result.upload_urls) {
    const fileData = fs.readFileSync(urlInfo.file_name);
    await fetch(urlInfo.upload_url, {
      method: urlInfo.method,
      headers: urlInfo.headers,
      body: fileData,
    });
  }

  // Confirm the transfer
  await fetch(result.confirm_url, {
    method: "POST",
    headers: { Authorization: "Bearer agd_live_xxxxxxxxxxxxxxxxxxxx" },
  });
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "id": "tr_4d2e8f1a-6b3c-4a7e-9d0f-5e8c1b2a3d4e",
  "upload_urls": [
    {
      "file_id": "file_a1b2c3d4",
      "file_name": "report.pdf",
      "upload_url": "https://storage.agent-drop.com/uploads/tr_4d2e8f1a.../report.pdf?X-Amz-Signature=...",
      "method": "PUT",
      "headers": { "Content-Type": "application/pdf" }
    },
    {
      "file_id": "file_e5f6g7h8",
      "file_name": "data.csv",
      "upload_url": "https://storage.agent-drop.com/uploads/tr_4d2e8f1a.../data.csv?X-Amz-Signature=...",
      "method": "PUT",
      "headers": { "Content-Type": "text/csv" }
    }
  ],
  "confirm_url": "https://api.agent-drop.com/v1/transfers/tr_4d2e8f1a-6b3c-4a7e-9d0f-5e8c1b2a3d4e/confirm",
  "expires_at": "2026-03-28T13:00:00Z"
}
```

## Errors

| Status | Code               | Description                               |
| ------ | ------------------ | ----------------------------------------- |
| `400`  | `VALIDATION_ERROR` | Missing required fields or invalid values |
| `401`  | `UNAUTHORIZED`     | Invalid or missing API key                |
| `413`  | `FILE_TOO_LARGE`   | File size exceeds your plan's limit       |
| `429`  | `RATE_LIMITED`     | Too many requests. Back off and retry.    |
