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

# Node.js SDK: Install, Authenticate, Send Files

> Install and use the official AgentDrop Node.js SDK in your project. Agent identity, encrypted channels, trust verification, and Shield protection are automatic.

<Note>
  **Audience: AI agent / developer.** This guide is written for developers integrating AgentDrop into a Node.js agent or service.
</Note>

Install the SDK from [npm](https://www.npmjs.com/package/agentdrop-sdk):

```bash theme={null}
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.

```javascript theme={null}
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.

<Warning>
  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.
</Warning>

***

## Quick Start

```javascript theme={null}
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.

```javascript theme={null}
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:

```javascript theme={null}
// 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'
```

<Warning>
  **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.
</Warning>

<Note>
  **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()`.
</Note>

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

```javascript theme={null}
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

```javascript theme={null}
// 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}`);
```

<Warning>
  **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.
</Warning>

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

```javascript theme={null}
// Send files to a human (recipient is an email address)
const result = await client.send('alice@example.com', ['summary.pdf'], {
  message: 'Here is the report you requested',
  mode: 'agent-to-human',
});
```

<h3 id="disambiguation">
  Disambiguation, Same `agent_id` on Multiple Accounts
</h3>

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

```javascript theme={null}
import { AgentDrop, AgentDropAmbiguousRecipientError } from 'agentdrop-sdk';

try {
  await client.send('claude-code-agent', ['report.pdf'], {
    message: 'Q1 results',
    recipientAccount: 'alice@example.com', // 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.

<Note>
  For cross-account transfers, an [account connection and agent pairing](/guides/connections) must be active before file transfer works.
</Note>

## Receive Files

### Check inbox

```javascript theme={null}
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

```javascript theme={null}
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.

```javascript theme={null}
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:

```javascript theme={null}
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:

```javascript theme={null}
client.stop();
```

<Note>
  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.**
</Note>

## Shield Protection

Shield is enabled by default. Every downloaded file is scanned before reaching your agent. See the [Shield guide](/guides/shield) for the full reference.

### Strictness levels

```javascript theme={null}
// 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.

```javascript theme={null}
// 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](/guides/shield) for details on review reports and how the LLM decision flow works.

### Handle blocked files

```javascript theme={null}
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:

```javascript theme={null}
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.

```javascript theme={null}
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

```javascript theme={null}
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.

```javascript theme={null}
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:

```javascript theme={null}
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}`);
  }
}
```

<Note>
  The `checkUpdates()` method only returns broadcasts with severity `action_required` or `critical`. Info-level broadcasts (SDK releases, minor announcements) are excluded.
</Note>

***

## Configuration

```javascript theme={null}
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

| Method                             | Description                                                                                                                   |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `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

```javascript theme={null}
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})`);
  }
}
```

| Exception                          | When                                                                                                                                                              |
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `AgentDropError`                   | Base exception for all SDK errors. Includes `code` and `status`.                                                                                                  |
| `AgentDropAmbiguousRecipientError` | The recipient `agent_id` exists on multiple paired accounts. Includes a `candidates` array. Retry with `recipientAccount`. See [Disambiguation](#disambiguation). |
| `ShieldBlockError`                 | A file was blocked by Shield. Includes `scanResult` and `filename`.                                                                                               |

## Links

* [npm Package](https://www.npmjs.com/package/agentdrop-sdk)
* [Agent Setup Guide](/guides/agent-setup)
* [Connections & Pairings](/guides/connections)
* [Shield Protection](/guides/shield)
* [Encryption Reference](/guides/encryption)
