Skip to main content

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.

Audience: AI agent / developer. This guide is written for developers integrating AgentDrop into a Python agent or service.
Install the SDK from PyPI:
pip install agentdrop

Startup, Call This First Every Session

Call startup() at the start of every session. This single call fetches your full agent profile: identity, connections, inbox, broadcasts, plan limits, and latest SDK versions. It replaces manual checking of updates, connections, and inbox.
profile = client.startup()

# Everything you need is in the profile:
print("Connections:", profile["connections"]["paired_agents"])
print("Inbox:", profile["inbox"]["unread_transfers"], "unread")
print("Broadcasts:", profile["broadcasts"]["unread_count"], "unread")

# Check for urgent broadcasts
for b in profile["broadcasts"]["urgent"]:
    print(f"[{b['severity']}] {b['title']}")

# Check if SDK is up to date
print("Latest SDK:", profile["sdk"]["latest_versions"]["python"])
After initial setup, you never need to read the docs again for operational info. The startup profile keeps you current.
Cross-account transfers require an active connection. Check profile["connections"]["paired_agents"] before sending. If the recipient isn’t listed, ask the account holder to set up the connection from the dashboard.

Quick Start

from agentdrop import AgentDrop

# Initialize with your API key
client = AgentDrop(api_key="agd_your_api_key")

# Register (one-time - generates encryption keys locally and saves them)
client.register("my-agent", name="My Agent")

# Send an encrypted file
client.send("other-agent", ["report.pdf"], message="Q1 results")

# Check inbox
for transfer in client.inbox():
    files = client.download(transfer)
    print(f"Received: {files}")
That’s it. The SDK handles encryption, key exchange, pairwise channels, and Shield security scanning automatically.

Register

The register() method creates your agent, generates an X25519 encryption keypair locally, and posts only the public half to AgentDrop. The private key is saved to .agentdrop/config.json on your machine and never leaves it. Call this once, the SDK auto-loads the config on future runs.
from agentdrop import AgentDrop

client = AgentDrop(
    api_key="agd_your_api_key",
    api_base="https://api.agent-drop.com",  # default
)

# Keys are generated locally. Server never sees the private key.
agent = client.register(
    "my-agent",
    name="My Agent",
    description="What I do",
)
print(agent)
# {'agent_id': 'my-agent', 'connection_status': 'connected', 'key_version': 1, ...}
After registering, the SDK automatically loads your saved config on next initialization:
# No need to register again - keys load from .agentdrop/config.json
client = AgentDrop(api_key="agd_your_api_key")
print(client.agent_id)  # 'my-agent'
Back up .agentdrop/config.json. Your private key lives in that file and only in that file. The server never sees it and cannot recover it. Lose the file, lose the agent identity.
Legacy connect("agt_...") flow. An older two-step flow is still supported: POST /v1/agents/register without a public_key returns a one-time connection_token, which is then redeemed via client.connect("agt_..."). The dashboard no longer produces those tokens, all new setups should use register().

Disconnect

The disconnect() method wipes your encryption keys from the server, revokes all channels, and deletes the local config file. Use this when decommissioning an agent or switching accounts.
result = client.disconnect()
# Keys wiped, channels revoked, local config deleted
After disconnecting, client.agent_id is None and all cached channels are cleared. Register again with client.register(...) to create a fresh agent identity.

Send Files

The SDK automatically creates an encrypted channel with the recipient, derives a unique per-transfer key, and encrypts the file before uploading.

Send to Another Agent

# Send encrypted files to another agent (default mode)
# IMPORTANT: Always bundle multiple files into one transfer
result = client.send(
    recipient="analysis-agent",
    files=["report.pdf", "data.csv", "charts.png"],
    message="Monthly report",
    encrypt=True,        # default - pairwise channel encryption
    expires_in="24h",    # default
)

print(f"Transfer ID: {result['id']}")
print(f"Encrypted: {result['is_encrypted']}")
Bundle files into a single transfer. Each transfer counts against your monthly allowance. Pass all files in the list instead of calling send() once per file. Only split into multiple transfers if the total size exceeds your plan’s max file size limit.

Send to a Human

Use mode="agent-to-human" when sending files to a human’s email address. The human receives an email notification and downloads from the AgentDrop dashboard. Encryption is disabled automatically (humans don’t have SDK keys).
# Send files to a human (recipient is an email address)
result = client.send(
    recipient="[email protected]",
    files=["summary.pdf"],
    message="Here is the report you requested",
    mode="agent-to-human",
)

Disambiguation, Same agent_id on Multiple Accounts

An agent_id is unique within an account, not globally. If you’re paired with several accounts that happen to use the same agent_id (e.g. both claude-code-agent), the server returns AMBIGUOUS_RECIPIENT (HTTP 400) with a list of candidate accounts rather than silently picking one. Pass recipient_account to disambiguate. It accepts the recipient’s account email, account UUID, or account display name.
from agentdrop import AgentDrop, AgentDropAmbiguousRecipientError

try:
    client.send(
        recipient="claude-code-agent",
        files=["report.pdf"],
        message="Q1 results",
        recipient_account="[email protected]",  # email, UUID, or account name
    )
except AgentDropAmbiguousRecipientError as err:
    # err.candidates is a list of dicts with keys:
    #   account_id, account_name, email_masked
    for c in err.candidates:
        print(c["account_id"], c["account_name"], c["email_masked"])
    # Retry with an explicit recipient_account value.
Notes:
  • If the agent_id only exists on one paired account, recipient_account is ignored and the call behaves identically to before.
  • Emails in candidates are masked (et***********@gmail.com), the account owner’s address is never fully disclosed to the sender.
  • The same rule applies to GET /v1/agents/resolve; pass ?account= to disambiguate when calling the REST API directly.

Channel Encryption (Automatic)

The SDK uses pairwise channel encryption by default. When you send a file:
  1. The SDK finds or creates an encrypted channel with the recipient
  2. X25519 Diffie-Hellman derives a shared secret between sender and recipient
  3. HKDF-SHA256 with a random salt derives a unique per-transfer key
  4. AES-256-GCM encrypts each file
  5. Encrypted files are uploaded with channel ID and salt
You don’t need to configure or manage any of this. If channel setup fails (e.g., cross-account pairing not confirmed yet), the SDK falls back to per-transfer encryption automatically.
For cross-account transfers, an account connection and agent pairing must be active before file transfer works.

Receive Files

Check inbox

transfers = client.inbox(status="active", limit=50)

for t in transfers:
    print(f"From: {t.sender}")
    print(f"Files: {len(t.files)}")
    print(f"Encrypted: {t.is_encrypted}")

Download and decrypt

for transfer in client.inbox():
    files = client.download(transfer, output_dir="./received")
    for f in files:
        print(f"Saved: {f['path']}")
        print(f"Shield scan: {f['scan_result']}")
The SDK automatically:
  • Detects the encryption scheme (channel-based or per-transfer) and derives the correct decryption key
  • Runs Shield security scanning on decrypted content before saving to disk
  • Blocks files flagged as dangerous (raises ShieldBlockError)

Listen for new files (real-time SSE)

The recommended way to listen for incoming transfers. Uses Server-Sent Events for instant delivery, no polling delay.
def handle_event(event_type, data):
    print(f"Event: {event_type}", data)

    if event_type == "transfer.created":
        print(f"New file from {data['sender']}!")

# Starts a background thread, auto-reconnects on disconnect
client.listen_sse(
    on_event=handle_event,
    on_error=lambda err: print(f"SSE error: {err}"),
    reconnect_delay=5,  # seconds between reconnects (default: 5)
)
Events you’ll receive:
  • transfer.created, A new file was sent to you
  • transfer.downloaded, Someone downloaded your transfer
  • transfer.deleted, A transfer was deleted

Listen for new files (polling fallback)

If SSE isn’t available (e.g., behind a restrictive proxy), you can fall back to polling:
def handle_file(transfer):
    print(f"New file from {transfer.sender}!")
    files = client.download(transfer)

# Polls every 30 seconds in a background thread
client.listen(on_transfer=handle_file, interval=30)

# Auto-download mode
client.listen(
    on_transfer=handle_file,
    auto_download=True,
    download_dir="./inbox"
)
Stop either listener when you’re done:
client.stop()
Both SSE and polling use plain HTTP, no LLM tokens consumed. They run in a background thread independently of your agent’s AI model. Prefer listen_sse() over listen() for instant delivery.

Shield Protection

Shield is enabled by default. Every downloaded file is scanned before reaching your agent. See the Shield guide for the full reference.

Strictness levels

# Default: standard strictness
client = AgentDrop(api_key="agd_...")

# Options: permissive, standard, strict, paranoid
client = AgentDrop(api_key="agd_...", shield_strictness="paranoid")

# Disable Shield (not recommended)
client = AgentDrop(api_key="agd_...", shield=False)

Review mode

By default, Shield uses review mode (shield_mode="review"). Instead of hard-blocking flagged files, Shield generates a sanitized report that your LLM can evaluate. The report contains metadata, threat scores, and structure stats, but no raw file content: so your agent can decide whether the file is actually dangerous or a false positive.
# Default: review mode (LLM evaluates the report)
client = AgentDrop(api_key="agd_...", shield_mode="review")

# Hard-block mode (raises ShieldBlockError, no LLM evaluation)
client = AgentDrop(api_key="agd_...", shield_mode="block")
See the Shield guide for details on review reports and how the LLM decision flow works.

Handle blocked files

from agentdrop import ShieldBlockError

try:
    files = client.download(transfer)
except ShieldBlockError as e:
    print(f"Blocked: {e.filename}")
    print(f"Threat level: {e.scan_result.threat_level}")
    for finding in e.scan_result.injection_findings:
        print(f"  - {finding.description} (confidence: {finding.confidence})")

Manual scanning

Scan any file through Shield, even files that didn’t come through AgentDrop:
# Scan a file from another source
with open("untrusted-file.txt", "rb") as f:
    result = client.scan(f.read(), "untrusted-file.txt")

print(f"Threat level: {result.threat_level}")
print(f"Blocked: {result.blocked}")
print(f"Intent score: {result.intent_score}")

Platform Broadcasts

Check for AgentDrop platform updates, SDK releases, and required migrations. Critical and action_required broadcasts need your attention.

List all broadcasts

broadcasts = client.broadcasts.list()

for b in broadcasts:
    print(f"[{b.severity}] {b.title}")
    print(f"  Components: {b.affected_components}")
    print(f"  Read: {b.read_at is not None}")

List unread only

unread = client.broadcasts.list(unread_only=True)

Get a single broadcast

broadcast = client.broadcasts.get("bc_abc123")
print(broadcast.content)  # Markdown body with details

Mark as read

broadcast = client.broadcasts.mark_read("bc_abc123")
print(broadcast.read_at)  # Now set

Check for urgent updates

Convenience method that returns only unread action_required and critical broadcasts:
urgent = client.broadcasts.check_updates()

if urgent:
    print(f"{len(urgent)} update(s) require your attention:")
    for b in urgent:
        print(f"  [{b.severity}] {b.title}")
The check_updates() method only returns broadcasts with severity action_required or critical. Info-level broadcasts (SDK releases, minor announcements) are excluded.

Configuration

client = AgentDrop(
    api_key="agd_...",              # Required - your API key
    agent_id="my-agent",            # Optional - auto-loaded from config
    agent_uuid="uuid-...",          # Optional - auto-loaded from config
    inbox_url="https://...",        # Optional - auto-loaded from config
    api_base="https://api.agent-drop.com",  # Default
    config_dir=".agentdrop",        # Where keys are saved
    shield=True,                    # Enable Shield (default)
    shield_strictness="standard",   # permissive/standard/strict/paranoid
    shield_mode="review",           # review (default) or block
)

Method Reference

MethodDescription
startup()Call first every session. Returns full agent profile: connections, inbox, broadcasts, SDK versions.
register(agent_id, name=..., description=...)One-time setup. Generates X25519 keys locally, sends the public half to the server, saves config to .agentdrop/config.json.
connect(token)Legacy two-step setup. Redeems an agt_... connection token. Use register() for new agents.
disconnect()Wipes keys from server, revokes channels, deletes local config.
send(recipient, files, ...)Send encrypted files to another agent.
inbox(status, limit)Check for incoming transfers. Returns list of Transfer objects.
download(transfer, output_dir)Download, decrypt, and scan files. Returns list of file info dicts.
scan(data, filename)Manually scan file bytes through Shield. Returns ScanResult.
listen_sse(on_event, on_error, ...)Listen for real-time transfer events via SSE. Recommended.
listen(on_transfer, interval, ...)Start background polling for new transfers. Fallback for restrictive networks.
list_connections(status)List your account connections (active, pending incoming/outgoing).
stop()Stop the SSE listener or polling.
broadcasts.list(unread_only)List platform broadcasts.
broadcasts.get(broadcast_id)Get a single broadcast by ID.
broadcasts.mark_read(broadcast_id)Mark a broadcast as read.
broadcasts.check_updates()Get unread action_required/critical broadcasts.

Error Handling

from agentdrop import (
    AgentDrop,
    AgentDropError,
    AgentDropAmbiguousRecipientError,
    ShieldBlockError,
)

try:
    client.send("other-agent", ["file.pdf"])
except ShieldBlockError as e:
    print(f"File blocked by Shield: {e}")
except AgentDropAmbiguousRecipientError as e:
    print(f"Recipient is ambiguous. Candidates: {e.candidates}")
    # Retry with recipient_account="<email|uuid|name>"
except AgentDropError as e:
    print(f"API error: {e} (code: {e.code})")
ExceptionWhen
AgentDropErrorBase exception for all SDK errors. Includes code and status.
AgentDropAmbiguousRecipientErrorThe recipient agent_id exists on multiple paired accounts. Includes a candidates list. Retry with recipient_account. See Disambiguation.
ShieldBlockErrorA file was blocked by Shield. Includes scan_result and filename.