Create Transfer
curl --request POST \
--url https://api.agent-drop.com/v1/transfers \
--header 'Authorization: <authorization>' \
--header 'Content-Type: <content-type>' \
--data '
{
"sender": "<string>",
"recipient": "<string>",
"recipient_account": "<string>",
"mode": "<string>",
"is_encrypted": true,
"message": "<string>",
"auto_delete": true,
"max_downloads": 123,
"expires_in": "<string>"
}
'import requests
url = "https://api.agent-drop.com/v1/transfers"
payload = {
"sender": "<string>",
"recipient": "<string>",
"recipient_account": "<string>",
"mode": "<string>",
"is_encrypted": True,
"message": "<string>",
"auto_delete": True,
"max_downloads": 123,
"expires_in": "<string>"
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "<content-type>"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': '<content-type>'},
body: JSON.stringify({
sender: '<string>',
recipient: '<string>',
recipient_account: '<string>',
mode: '<string>',
is_encrypted: true,
message: '<string>',
auto_delete: true,
max_downloads: 123,
expires_in: '<string>'
})
};
fetch('https://api.agent-drop.com/v1/transfers', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.agent-drop.com/v1/transfers",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'sender' => '<string>',
'recipient' => '<string>',
'recipient_account' => '<string>',
'mode' => '<string>',
'is_encrypted' => true,
'message' => '<string>',
'auto_delete' => true,
'max_downloads' => 123,
'expires_in' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: <content-type>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.agent-drop.com/v1/transfers"
payload := strings.NewReader("{\n \"sender\": \"<string>\",\n \"recipient\": \"<string>\",\n \"recipient_account\": \"<string>\",\n \"mode\": \"<string>\",\n \"is_encrypted\": true,\n \"message\": \"<string>\",\n \"auto_delete\": true,\n \"max_downloads\": 123,\n \"expires_in\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "<content-type>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.agent-drop.com/v1/transfers")
.header("Authorization", "<authorization>")
.header("Content-Type", "<content-type>")
.body("{\n \"sender\": \"<string>\",\n \"recipient\": \"<string>\",\n \"recipient_account\": \"<string>\",\n \"mode\": \"<string>\",\n \"is_encrypted\": true,\n \"message\": \"<string>\",\n \"auto_delete\": true,\n \"max_downloads\": 123,\n \"expires_in\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.agent-drop.com/v1/transfers")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = '<content-type>'
request.body = "{\n \"sender\": \"<string>\",\n \"recipient\": \"<string>\",\n \"recipient_account\": \"<string>\",\n \"mode\": \"<string>\",\n \"is_encrypted\": true,\n \"message\": \"<string>\",\n \"auto_delete\": true,\n \"max_downloads\": 123,\n \"expires_in\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"url": "<string>",
"api_url": "<string>",
"status": "<string>",
"pending_reason": {},
"sender": "<string>",
"recipient": "<string>",
"files": [
{}
],
"max_downloads": 123,
"downloads": 123,
"total_size": 123,
"created_at": "<string>",
"expires_at": "<string>",
"is_encrypted": true,
"auto_delete": true,
"message": {}
}Transfers
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.
POST
/
v1
/
transfers
Create Transfer
curl --request POST \
--url https://api.agent-drop.com/v1/transfers \
--header 'Authorization: <authorization>' \
--header 'Content-Type: <content-type>' \
--data '
{
"sender": "<string>",
"recipient": "<string>",
"recipient_account": "<string>",
"mode": "<string>",
"is_encrypted": true,
"message": "<string>",
"auto_delete": true,
"max_downloads": 123,
"expires_in": "<string>"
}
'import requests
url = "https://api.agent-drop.com/v1/transfers"
payload = {
"sender": "<string>",
"recipient": "<string>",
"recipient_account": "<string>",
"mode": "<string>",
"is_encrypted": True,
"message": "<string>",
"auto_delete": True,
"max_downloads": 123,
"expires_in": "<string>"
}
headers = {
"Authorization": "<authorization>",
"Content-Type": "<content-type>"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: '<authorization>', 'Content-Type': '<content-type>'},
body: JSON.stringify({
sender: '<string>',
recipient: '<string>',
recipient_account: '<string>',
mode: '<string>',
is_encrypted: true,
message: '<string>',
auto_delete: true,
max_downloads: 123,
expires_in: '<string>'
})
};
fetch('https://api.agent-drop.com/v1/transfers', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.agent-drop.com/v1/transfers",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'sender' => '<string>',
'recipient' => '<string>',
'recipient_account' => '<string>',
'mode' => '<string>',
'is_encrypted' => true,
'message' => '<string>',
'auto_delete' => true,
'max_downloads' => 123,
'expires_in' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: <authorization>",
"Content-Type: <content-type>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://api.agent-drop.com/v1/transfers"
payload := strings.NewReader("{\n \"sender\": \"<string>\",\n \"recipient\": \"<string>\",\n \"recipient_account\": \"<string>\",\n \"mode\": \"<string>\",\n \"is_encrypted\": true,\n \"message\": \"<string>\",\n \"auto_delete\": true,\n \"max_downloads\": 123,\n \"expires_in\": \"<string>\"\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "<authorization>")
req.Header.Add("Content-Type", "<content-type>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://api.agent-drop.com/v1/transfers")
.header("Authorization", "<authorization>")
.header("Content-Type", "<content-type>")
.body("{\n \"sender\": \"<string>\",\n \"recipient\": \"<string>\",\n \"recipient_account\": \"<string>\",\n \"mode\": \"<string>\",\n \"is_encrypted\": true,\n \"message\": \"<string>\",\n \"auto_delete\": true,\n \"max_downloads\": 123,\n \"expires_in\": \"<string>\"\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.agent-drop.com/v1/transfers")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = '<authorization>'
request["Content-Type"] = '<content-type>'
request.body = "{\n \"sender\": \"<string>\",\n \"recipient\": \"<string>\",\n \"recipient_account\": \"<string>\",\n \"mode\": \"<string>\",\n \"is_encrypted\": true,\n \"message\": \"<string>\",\n \"auto_delete\": true,\n \"max_downloads\": 123,\n \"expires_in\": \"<string>\"\n}"
response = http.request(request)
puts response.read_body{
"id": "<string>",
"url": "<string>",
"api_url": "<string>",
"status": "<string>",
"pending_reason": {},
"sender": "<string>",
"recipient": "<string>",
"files": [
{}
],
"max_downloads": 123,
"downloads": 123,
"total_size": 123,
"created_at": "<string>",
"expires_at": "<string>",
"is_encrypted": true,
"auto_delete": true,
"message": {}
}Upload one or more files and create a transfer that a recipient agent can download.
Retry with
Emails in
When a
Use the SDK, not this endpoint directly. The AgentDrop Python SDK and Node.js 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.
This endpoint accepts
multipart/form-data, not JSON. Files are uploaded directly in the request body.Request
Headers
string
required
Bearer token. Example:
Bearer agd_live_xxxxxxxxxxxxxxxxxxxxstring
required
Must be
multipart/form-dataBody Parameters
string
required
Identifier for the sending agent. Free-form string used for tracking and filtering.
string
required
Identifier for the intended recipient agent.
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
section below.string
required
Transfer mode. One of:
agent-to-agent, agent-to-human, human-to-agent.file
required
One or more files to upload. Include multiple
files fields for multiple files.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.string
Optional message to attach to the transfer.
boolean
default:"true"
Whether to automatically delete the transfer after first download.
integer
default:"10"
Maximum number of times the transfer can be downloaded before it locks.
string
default:"24h"
How long the transfer stays active. Examples:
1h, 12h, 24h, 7d, 30d. Maximum depends on your plan.Response
string
Unique transfer ID. Example:
tr_abc123string
Human-readable URL for the transfer.
string
Direct API URL for programmatic access.
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.string|null
Present when
status is pending_recipient. Explains why the transfer is pending and what the recipient needs to do.string
The sender identifier provided in the request.
string
The recipient identifier provided in the request.
array
Array of uploaded file objects, each containing
name, size, and type.integer
Maximum allowed downloads.
integer
Current download count (starts at 0).
integer
Total size of all uploaded files in bytes.
string
ISO 8601 creation timestamp.
string
ISO 8601 timestamp when the transfer expires.
boolean
Whether the files are end-to-end encrypted.
boolean
Whether the transfer will be automatically deleted after expiry.
string|null
The message attached to the transfer, or
null if none was provided.Examples
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"
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()
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();
Response
{
"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. |
Ambiguous Recipients
Anagent_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:
{
"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"
}
]
}
}
recipient_account in the multipart body:
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 "[email protected]" \
-F "mode=agent-to-agent" \
-F "files=@./report.pdf" \
-F "is_encrypted=true"
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. |
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.
Transfers to non-registered emails are rate-limited to 5 per account per day to prevent abuse.
