> ## 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.

# Create Transfer: Initiate a New File Transfer

> Create an AgentDrop file transfer with one or more files. SDK encrypts everything client-side using X25519 + AES-256-GCM; server never sees plaintext.

Upload one or more files and create a transfer that a recipient agent can download.

<Warning>
  **Use the SDK, not this endpoint directly.** The AgentDrop [Python SDK](/guides/python-sdk) and [Node.js SDK](/guides/node-sdk) wrap this endpoint and handle X25519 key exchange, AES-256-GCM encryption, pairwise channel derivation, and Shield scanning automatically. Calling this endpoint directly skips all of that, your files will be uploaded as plaintext and downloads will not be scanned for prompt injection or malware. The endpoint is documented here for transparency, not because raw HTTP is a supported integration path.
</Warning>

<Note>
  This endpoint accepts `multipart/form-data`, not JSON. Files are uploaded directly in the request body.
</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 `multipart/form-data`
</ParamField>

### Body Parameters

<ParamField body="sender" type="string" required>
  Identifier for the sending agent. Free-form string used for tracking and filtering.
</ParamField>

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

<ParamField body="recipient_account" type="string">
  Disambiguator when the same `agent_id` exists on multiple paired accounts.
  Accepts the recipient's account email, account UUID, or account display
  name. Required only when `/v1/agents/resolve` returns `AMBIGUOUS_RECIPIENT`
  for the recipient. See the [Ambiguous Recipients](#ambiguous-recipients)
  section below.
</ParamField>

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

<ParamField body="files" type="file" required>
  One or more files to upload. Include multiple `files` fields for multiple files.
</ParamField>

<ParamField body="is_encrypted" type="boolean" default="false">
  Whether the uploaded files are already encrypted with AES-256-GCM (done client-side before upload). The SDK always sets this to `true` and encrypts before calling the endpoint. If you call the API directly with `is_encrypted=false`, your files are stored on our servers as plaintext, do not do this for any file that matters.
</ParamField>

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

<ParamField body="auto_delete" type="boolean" default="true">
  Whether to automatically delete the transfer after first download.
</ParamField>

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

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

## Response

<ResponseField name="id" type="string">
  Unique transfer ID. Example: `tr_abc123`
</ResponseField>

<ResponseField name="url" type="string">
  Human-readable URL for the transfer.
</ResponseField>

<ResponseField name="api_url" type="string">
  Direct API URL for programmatic access.
</ResponseField>

<ResponseField name="status" type="string">
  Transfer status. One of: `active`, `pending_recipient`, `expired`, `deleted`.

  `pending_recipient` means the recipient doesn't have an AgentDrop account or hasn't set a receiving password yet. They'll be notified by email. The transfer activates automatically when they sign up and set their receiving password.
</ResponseField>

<ResponseField name="pending_reason" type="string|null">
  Present when `status` is `pending_recipient`. Explains why the transfer is pending and what the recipient needs to do.
</ResponseField>

<ResponseField name="sender" type="string">
  The sender identifier provided in the request.
</ResponseField>

<ResponseField name="recipient" type="string">
  The recipient identifier provided in the request.
</ResponseField>

<ResponseField name="files" type="array">
  Array of uploaded file objects, each containing `name`, `size`, and `type`.
</ResponseField>

<ResponseField name="max_downloads" type="integer">
  Maximum allowed downloads.
</ResponseField>

<ResponseField name="downloads" type="integer">
  Current download count (starts at 0).
</ResponseField>

<ResponseField name="total_size" type="integer">
  Total size of all uploaded files in bytes.
</ResponseField>

<ResponseField name="created_at" type="string">
  ISO 8601 creation timestamp.
</ResponseField>

<ResponseField name="expires_at" type="string">
  ISO 8601 timestamp when the transfer expires.
</ResponseField>

<ResponseField name="is_encrypted" type="boolean">
  Whether the files are end-to-end encrypted.
</ResponseField>

<ResponseField name="auto_delete" type="boolean">
  Whether the transfer will be automatically deleted after expiry.
</ResponseField>

<ResponseField name="message" type="string|null">
  The message attached to the transfer, or `null` if none was provided.
</ResponseField>

## Examples

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://api.agent-drop.com/v1/transfers \
    -H "Authorization: Bearer agd_live_xxxxxxxxxxxxxxxxxxxx" \
    -F "sender=data-pipeline" \
    -F "recipient=analysis-agent" \
    -F "mode=agent-to-agent" \
    -F "files=@./report.pdf" \
    -F "files=@./data.csv" \
    -F "is_encrypted=true" \
    -F "message=Weekly report files" \
    -F "auto_delete=true" \
    -F "max_downloads=3" \
    -F "expires_in=12h"
  ```

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

  response = requests.post(
      "https://api.agent-drop.com/v1/transfers",
      headers={"Authorization": "Bearer agd_live_xxxxxxxxxxxxxxxxxxxx"},
      data={
          "sender": "data-pipeline",
          "recipient": "analysis-agent",
          "mode": "agent-to-agent",
          "is_encrypted": "true",
          "message": "Weekly report files",
          "auto_delete": "true",
          "max_downloads": "3",
          "expires_in": "12h",
      },
      files=[
          ("files", ("report.pdf", open("report.pdf", "rb"), "application/pdf")),
          ("files", ("data.csv", open("data.csv", "rb"), "text/csv")),
      ],
  )

  transfer = response.json()
  ```

  ```javascript Node.js theme={null}
  const form = new FormData();
  form.append("sender", "data-pipeline");
  form.append("recipient", "analysis-agent");
  form.append("mode", "agent-to-agent");
  form.append("is_encrypted", "true");
  form.append("message", "Weekly report files");
  form.append("auto_delete", "true");
  form.append("max_downloads", "3");
  form.append("expires_in", "12h");
  form.append("files", fs.createReadStream("report.pdf"));
  form.append("files", fs.createReadStream("data.csv"));

  const response = await fetch(
    "https://api.agent-drop.com/v1/transfers",
    {
      method: "POST",
      headers: { Authorization: "Bearer agd_live_xxxxxxxxxxxxxxxxxxxx" },
      body: form,
    }
  );

  const transfer = await response.json();
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "id": "tr_abc123",
  "url": "https://agent-drop.com/t/tr_abc123",
  "api_url": "https://api.agent-drop.com/v1/transfers/tr_abc123/download",
  "status": "active",
  "mode": "agent-to-agent",
  "sender": "data-pipeline",
  "recipient": "analysis-agent",
  "files": [
    { "name": "report.pdf", "size": 1048576, "type": "application/pdf" },
    { "name": "data.csv", "size": 245760, "type": "text/csv" }
  ],
  "max_downloads": 3,
  "downloads": 0,
  "total_size": 1294336,
  "created_at": "2026-03-22T12:00:00Z",
  "expires_at": "2026-03-22T00:00:00Z",
  "is_encrypted": true,
  "auto_delete": false,
  "message": "Weekly report files"
}
```

## Errors

| Status | Code                  | Description                                                                                                                                                                            |
| ------ | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `400`  | `VALIDATION_ERROR`    | Missing required fields or invalid values                                                                                                                                              |
| `400`  | `AMBIGUOUS_RECIPIENT` | The `recipient` agent\_id matches multiple paired accounts. Retry with `recipient_account` set to an email, account UUID, or account display name from the returned `candidates` list. |
| `401`  | `UNAUTHORIZED`        | Invalid or missing API key                                                                                                                                                             |
| `413`  | `FILE_TOO_LARGE`      | File exceeds your plan's max file size                                                                                                                                                 |
| `429`  | `RATE_LIMITED`        | Too many requests. Back off and retry.                                                                                                                                                 |
| `429`  | `INVITE_RATE_LIMIT`   | Daily limit (5) reached for transfers to non-registered recipients.                                                                                                                    |

<h2 id="ambiguous-recipients">
  Ambiguous Recipients
</h2>

An `agent_id` is unique **within** an account but can collide **across**
accounts you're paired with. If two paired accounts both use, say,
`claude-code-agent`, the server refuses to pick one and returns:

```json theme={null}
{
  "error": {
    "code": "AMBIGUOUS_RECIPIENT",
    "message": "Multiple agents with this agent_id exist across paired accounts. Pass `recipient_account` (email or account UUID) to disambiguate.",
    "status": 400,
    "candidates": [
      {
        "account_id": "ee9b1017-4d9d-457d-9006-6f70daa6bfd6",
        "account_name": "Alex Morgan",
        "email_masked": "et***********@gmail.com"
      },
      {
        "account_id": "1f2c2188-e1dc-45fc-900f-75f5c68b8e50",
        "account_name": "Jamie Chen",
        "email_masked": "as********@gmail.com"
      }
    ]
  }
}
```

Retry with `recipient_account` in the multipart body:

```bash theme={null}
curl -X POST https://api.agent-drop.com/v1/transfers \
  -H "Authorization: Bearer agd_live_xxxxxxxxxxxxxxxxxxxx" \
  -F "sender=my-agent" \
  -F "recipient=claude-code-agent" \
  -F "recipient_account=alice@example.com" \
  -F "mode=agent-to-agent" \
  -F "files=@./report.pdf" \
  -F "is_encrypted=true"
```

Emails in `candidates` are masked; the full address is never disclosed to
the sender. If the recipient's `agent_id` only exists on one paired
account, `recipient_account` is ignored.

## Agent-to-Human Transfers

When sending files to a human email (`mode: agent-to-human`), the behavior depends on the recipient's account status:

| Recipient Status                    | What Happens                                                                                                       |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| Registered + receiving password set | Files encrypted with recipient's X25519 public key (E2E). Status: `active`.                                        |
| Registered, no receiving password   | Files encrypted with server key. Recipient gets a nudge email to set their password. Status: `pending_recipient`.  |
| Not registered                      | Files encrypted with server key. Recipient gets an invite email to create an account. Status: `pending_recipient`. |

When a `pending_recipient` transfer activates (recipient signs up + sets password), files are re-encrypted with their public key and the server key is deleted. The recipient gets a "files ready" notification.

<Note>
  Transfers to non-registered emails are rate-limited to 5 per account per day to prevent abuse.
</Note>
