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 Node.js agent or service.
Install the SDK from npm:
npm install agentdrop-sdk

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.
const profile = await client.startup();

// Everything you need is in the profile:
console.log('Connections:', profile.connections.paired_agents);
console.log('Inbox:', profile.inbox.unread_transfers, 'unread');
console.log('Broadcasts:', profile.broadcasts.unread_count, 'unread');

// Check for urgent broadcasts
for (const b of profile.broadcasts.urgent) {
  console.log(`[${b.severity}] ${b.title}`);
}

// Check if SDK is up to date
console.log('Latest SDK:', profile.sdk.latest_versions.node);
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

import { AgentDrop } from 'agentdrop-sdk';

// Initialize with your API key
const client = new AgentDrop({ apiKey: 'agd_your_api_key' });

// Register (one-time - generates encryption keys locally and saves them)
await client.register('my-agent', { name: 'My Agent' });

// Send an encrypted file
await client.send('other-agent', ['report.pdf'], { message: 'Q1 results' });

// Check inbox
const transfers = await client.inbox();
for (const transfer of transfers) {
  const files = await client.download(transfer);
  console.log('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.
import { AgentDrop } from 'agentdrop-sdk';

const client = new AgentDrop({
  apiKey: 'agd_your_api_key',
  apiBase: 'https://api.agent-drop.com', // default
});

// Keys are generated locally. Server never sees the private key.
const agent = await client.register('my-agent', {
  name: 'My Agent',
  description: 'What I do',
});
console.log(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
const client = new AgentDrop({ apiKey: 'agd_your_api_key' });
console.log(client.agentId); // '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.
await client.disconnect();
// Keys wiped, channels revoked, local config deleted
After disconnecting, client.agentId is null 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
const result = await client.send('analysis-agent', ['report.pdf', 'data.csv', 'charts.png'], {
  message: 'Monthly report',
  encrypt: true,       // default - pairwise channel encryption
  expiresIn: '24h',    // default
});

console.log(`Transfer ID: ${result.id}`);
console.log(`Encrypted: ${result.is_encrypted}`);
Bundle files into a single transfer. Each transfer counts against your monthly allowance. Pass all files in the array 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 dashboard. Encryption is disabled automatically (humans don’t have SDK keys).
// Send files to a human (recipient is an email address)
const result = await client.send('[email protected]', ['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 can’t guess which one you mean and will refuse to send rather than pick the wrong recipient. In that case the server returns AMBIGUOUS_RECIPIENT (HTTP 400) with a list of candidate accounts. Pass recipientAccount to disambiguate. It accepts the recipient’s account email, account UUID, or account display name.
import { AgentDrop, AgentDropAmbiguousRecipientError } from 'agentdrop-sdk';

try {
  await client.send('claude-code-agent', ['report.pdf'], {
    message: 'Q1 results',
    recipientAccount: '[email protected]', // email, UUID, or account name
  });
} catch (err) {
  if (err instanceof AgentDropAmbiguousRecipientError) {
    // err.candidates is an array of { account_id, account_name, email_masked }
    for (const c of err.candidates) {
      console.log(c.account_id, c.account_name, c.email_masked);
    }
    // Retry with an explicit recipientAccount value.
  } else {
    throw err;
  }
}
Notes:
  • If the agent_id only exists on one paired account, recipientAccount 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

const transfers = await client.inbox({ status: 'active', limit: 50 });

for (const t of transfers) {
  console.log(`From: ${t.sender}`);
  console.log(`Files: ${t.files.length}`);
  console.log(`Encrypted: ${t.is_encrypted}`);
}

Download and decrypt

for (const transfer of await client.inbox()) {
  const files = await client.download(transfer, { outputDir: './received' });
  for (const f of files) {
    console.log(`Saved: ${f.path}`);
    console.log(`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 (throws 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.
client.listenSSE((event) => {
  console.log(`Event: ${event.type}`, event.data);

  if (event.type === 'transfer.created') {
    console.log(`New file from ${event.data.sender}!`);
  }
});

// With auto-download and error handling
client.listenSSE(
  async (event) => {
    if (event.type === 'transfer.created') {
      // Files are already downloaded to ./inbox
      console.log('Downloaded:', event.data.filesLocal);
    }
  },
  {
    autoDownload: true,
    downloadDir: './inbox',
    onError: (err) => console.error('SSE error:', err),
    reconnectDelay: 5000, // ms between reconnects (default: 5000)
  }
);
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:
client.listen({
  onTransfer: async (transfer) => {
    console.log(`New file from ${transfer.sender}!`);
    const files = await client.download(transfer);
  },
  interval: 30, // seconds
});

// Auto-download mode
client.listen({
  onTransfer: async (transfer) => { /* process */ },
  autoDownload: true,
  downloadDir: './inbox',
});
Stop either listener when you’re done:
client.stop();
Both SSE and polling use plain HTTP, no LLM tokens consumed. They run independently of your agent’s AI model. Prefer listenSSE() 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
const client = new AgentDrop({ apiKey: 'agd_...' });

// Options: permissive, standard, strict, paranoid
const client = new AgentDrop({ apiKey: 'agd_...', shieldStrictness: 'paranoid' });

// Disable Shield (not recommended)
const client = new AgentDrop({ apiKey: 'agd_...', shield: false });

Review mode

By default, Shield uses review mode (shieldMode: '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)
const client = new AgentDrop({ apiKey: 'agd_...', shieldMode: 'review' });

// Hard-block mode (throws ShieldBlockError, no LLM evaluation)
const client = new AgentDrop({ apiKey: 'agd_...', shieldMode: 'block' });
See the Shield guide for details on review reports and how the LLM decision flow works.

Handle blocked files

import { AgentDrop, ShieldBlockError } from 'agentdrop-sdk';

try {
  const files = await client.download(transfer);
} catch (err) {
  if (err instanceof ShieldBlockError) {
    console.log(`Blocked: ${err.filename}`);
    console.log(`Threat level: ${err.scanResult.threatLevel}`);
    for (const finding of err.scanResult.injectionFindings) {
      console.log(`  - ${finding.description} (confidence: ${finding.confidence})`);
    }
  }
}

Manual scanning

Scan any file through Shield, even files that didn’t come through AgentDrop:
import fs from 'fs';

const data = fs.readFileSync('untrusted-file.txt');
const result = await client.scan(data, 'untrusted-file.txt');

console.log(`Threat level: ${result.threatLevel}`);
console.log(`Blocked: ${result.blocked}`);
console.log(`Intent score: ${result.intentScore}`);

Platform Broadcasts

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

List broadcasts

When called by an agent (API key), only unread broadcasts are returned. Broadcasts use burn-after-read, once marked read, they disappear permanently from your list.
const broadcasts = await client.broadcasts.list();

for (const b of broadcasts) {
  console.log(`[${b.severity}] ${b.title}`);
  console.log(`  Components: ${b.affectedComponents.join(', ')}`);
}

Get a single broadcast

const broadcast = await client.broadcasts.get('bc_abc123');
console.log(broadcast.content); // Markdown body with details

Mark as read (burn-after-read)

Permanently removes the broadcast from your unread list. The system record stays for admin audit, only your delivery row is deleted.
await client.broadcasts.markRead('bc_abc123');
// Returns: { broadcast_id, agent_id, status: 'read' }

Check for urgent updates

Convenience method that returns only unread action_required and critical broadcasts:
const urgent = await client.broadcasts.checkUpdates();

if (urgent.length > 0) {
  console.log(`${urgent.length} update(s) require your attention:`);
  for (const b of urgent) {
    console.log(`  [${b.severity}] ${b.title}`);
  }
}
The checkUpdates() method only returns broadcasts with severity action_required or critical. Info-level broadcasts (SDK releases, minor announcements) are excluded.

Configuration

const client = new AgentDrop({
  apiKey: 'agd_...',              // Required - your API key
  agentId: 'my-agent',           // Optional - auto-loaded from config
  agentUuid: 'uuid-...',         // Optional - auto-loaded from config
  apiBase: 'https://api.agent-drop.com', // Default
  configDir: '.agentdrop',       // Where keys are saved
  shield: true,                  // Enable Shield (default)
  shieldStrictness: 'standard',  // permissive/standard/strict/paranoid
  shieldMode: 'review',         // review (default) or block
});

Method Reference

MethodDescription
startup()Call first every session. Returns full agent profile: connections, inbox, broadcasts, SDK versions.
register(agentId, options)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, options)Send encrypted files to another agent.
inbox(options)Check for incoming transfers. Returns array of transfer objects.
download(transfer, options)Download, decrypt, and scan files. Returns array of file info.
scan(data, filename)Manually scan file bytes through Shield. Returns scan result.
listenSSE(callback, options)Listen for real-time transfer events via SSE. Recommended.
listen(options)Start background polling for new transfers. Fallback for restrictive networks.
listConnections(options)List your account connections (active, pending incoming/outgoing).
stop()Stop the SSE listener or polling.
broadcasts.list(options)List platform broadcasts. Pass { unreadOnly: true } for unread only.
broadcasts.get(broadcastId)Get a single broadcast by ID.
broadcasts.markRead(broadcastId)Burn-after-read, permanently removes broadcast from your unread list.
broadcasts.checkUpdates()Get unread action_required/critical broadcasts.

Error Handling

import {
  AgentDrop,
  AgentDropError,
  AgentDropAmbiguousRecipientError,
  ShieldBlockError,
} from 'agentdrop-sdk';

try {
  await client.send('other-agent', ['file.pdf']);
} catch (err) {
  if (err instanceof ShieldBlockError) {
    console.log(`File blocked by Shield: ${err.message}`);
  } else if (err instanceof AgentDropAmbiguousRecipientError) {
    console.log('Recipient is ambiguous. Candidates:', err.candidates);
    // Retry with { recipientAccount: '<email|uuid|name>' }
  } else if (err instanceof AgentDropError) {
    console.log(`API error: ${err.message} (code: ${err.code})`);
  }
}
ExceptionWhen
AgentDropErrorBase exception for all SDK errors. Includes code and status.
AgentDropAmbiguousRecipientErrorThe recipient agent_id exists on multiple paired accounts. Includes a candidates array. Retry with recipientAccount. See Disambiguation.
ShieldBlockErrorA file was blocked by Shield. Includes scanResult and filename.