npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@inkbox/sdk

v0.2.10

Published

TypeScript SDK for the Inkbox API

Readme

@inkbox/sdk

TypeScript SDK for the Inkbox API — API-first communication infrastructure for AI agents (email, phone, identities, encrypted vault — login credentials, API keys, key pairs, SSH keys, OTP, etc.).

Install

npm install @inkbox/sdk

Requires Node.js ≥ 18.

Authentication

You'll need an API key to use this SDK. Get one at inkbox.ai/console.

Quick start

import { Inkbox } from "@inkbox/sdk";

const inkbox = await new Inkbox({
  apiKey: process.env.INKBOX_API_KEY!,
  vaultKey: process.env.INKBOX_VAULT_KEY,
}).ready();

// Create an agent identity with a linked mailbox
const identity = await inkbox.createIdentity("support-bot", { displayName: "Support Bot" });
const phone = await identity.provisionPhoneNumber({ type: "toll_free" });

// Send email directly from the identity
await identity.sendEmail({
  to: ["[email protected]"],
  subject: "Your order has shipped",
  bodyText: "Tracking number: 1Z999AA10123456784",
});

// Place an outbound call
await identity.placeCall({
  toNumber: "+18005559999",
  clientWebsocketUrl: "wss://my-app.com/voice",
});

// Read inbox
for await (const message of identity.iterEmails()) {
  console.log(message.subject);
}

// List calls
const calls = await identity.listCalls();

// Access credentials (vault unlocked at construction)
const creds = await identity.getCredentials();
for (const login of creds.listLogins()) {
  console.log(login.name);
}

Authentication

| Option | Type | Default | Description | |---|---|---|---| | apiKey | string | required | Your ApiKey_... token | | baseUrl | string | API default | Override for self-hosting or testing | | timeoutMs | number | 30000 | Request timeout in milliseconds |


Agent Signup

Agents can self-register without a pre-existing API key. All signup methods are static — no Inkbox instance required.

import { Inkbox } from "@inkbox/sdk";

// Sign up (public — no API key needed)
const result = await Inkbox.signup({
  humanEmail: "[email protected]",
  noteToHuman: "Hey John, this is your sales bot signing up!",
  displayName: "Sales Agent",      // optional
  agentHandle: "sales-agent",      // optional
  emailLocalPart: "sales.agent",   // optional
});
const apiKey = result.apiKey;          // save — shown only once
const email = result.emailAddress;     // e.g. "[email protected]"
const handle = result.agentHandle;     // e.g. "sales-agent-a1b2c3"

// Verify (after human shares the 6-digit code from the email)
await Inkbox.verifySignup(apiKey, { verificationCode: "483921" });

// Resend verification email (5-minute cooldown)
await Inkbox.resendSignupVerification(apiKey);

// Check status and restrictions
const status = await Inkbox.getSignupStatus(apiKey);
console.log(status.claimStatus);                    // "agent_unclaimed" or "agent_claimed"
console.log(status.restrictions.maxSendsPerDay);    // 10 (unclaimed) or 500 (claimed)

| Method | Auth | Returns | |---|---|---| | Inkbox.signup(request, options?) | None | AgentSignupResponse | | Inkbox.verifySignup(apiKey, request, options?) | API key | AgentSignupVerifyResponse | | Inkbox.resendSignupVerification(apiKey, options?) | API key | AgentSignupResendResponse | | Inkbox.getSignupStatus(apiKey, options?) | API key | AgentSignupStatusResponse |

request for signup() requires humanEmail and noteToHuman. displayName, agentHandle, and emailLocalPart are optional. All methods accept an optional options object with baseUrl and timeoutMs.

Note: Unclaimed agents can only send to the humanEmail specified at signup (max 10/day). After verification or human approval in the console, full capabilities are unlocked.

Note: The organizationId returned at signup is provisional (org_agent_...). It may change to a real organization ID after verification or human approval. Always use the organizationId from the most recent response (verifySignup or resendSignupVerification) rather than caching the value from the initial signup() call.


Identities

inkbox.createIdentity() and inkbox.getIdentity() return an AgentIdentity object that holds the identity's channels and exposes convenience methods scoped to those channels.

// Create and fully provision an identity
const identity = await inkbox.createIdentity("sales-bot", { displayName: "Sales Bot" });
const phone    = await identity.provisionPhoneNumber({ type: "toll_free" });      // provisions + links

console.log(identity.emailAddress);
console.log(phone.number);

// Link an existing mailbox or phone number instead of creating new ones
await identity.assignMailbox("mailbox-uuid-here");
await identity.assignPhoneNumber("phone-number-uuid-here");

// Get an existing identity (returned with current channel state)
const identity2 = await inkbox.getIdentity("sales-bot");
await identity2.refresh();  // re-fetch channels from API

// List all identities for your org
const allIdentities = await inkbox.listIdentities();

// Update status or handle
await identity.update({ status: "paused" });
await identity.update({ newHandle: "sales-bot-v2" });

// Unlink channels (without deleting them)
await identity.unlinkMailbox();
await identity.unlinkPhoneNumber();

// Delete
await identity.delete();

Mail

// Send an email (plain text and/or HTML)
const sent = await identity.sendEmail({
  to: ["[email protected]"],
  subject: "Hello from Inkbox",
  bodyText: "Hi there!",
  bodyHtml: "<p>Hi there!</p>",
  cc: ["[email protected]"],
  bcc: ["[email protected]"],
});

// Send a threaded reply
await identity.sendEmail({
  to: ["[email protected]"],
  subject: `Re: ${sent.subject}`,
  bodyText: "Following up!",
  inReplyToMessageId: sent.id,
});

// Send with attachments
await identity.sendEmail({
  to: ["[email protected]"],
  subject: "See attached",
  bodyText: "Please find the file attached.",
  attachments: [{
    filename: "report.pdf",
    contentType: "application/pdf",
    contentBase64: "<base64-encoded-content>",
  }],
});

// Iterate inbox (paginated automatically)
for await (const msg of identity.iterEmails()) {
  console.log(msg.subject, msg.fromAddress, msg.isRead);
}

// Filter by direction: "inbound" or "outbound"
for await (const msg of identity.iterEmails({ direction: "inbound" })) {
  console.log(msg.subject);
}

// Iterate only unread emails
for await (const msg of identity.iterUnreadEmails()) {
  console.log(msg.subject);
}

// Mark messages as read
const unread: string[] = [];
for await (const msg of identity.iterUnreadEmails()) unread.push(msg.id);
await identity.markEmailsRead(unread);

// Get all emails in a thread (threadId comes from msg.threadId)
const thread = await identity.getThread(msg.threadId!);
for (const m of thread.messages) {
  console.log(m.subject, m.fromAddress);
}

Phone

// Place an outbound call — stream audio over WebSocket
const call = await identity.placeCall({
  toNumber: "+15167251294",
  clientWebsocketUrl: "wss://your-agent.example.com/ws",
});
console.log(call.status, call.rateLimit.callsRemaining);

// List calls (paginated)
const calls = await identity.listCalls({ limit: 10, offset: 0 });
for (const c of calls) {
  console.log(c.id, c.direction, c.remotePhoneNumber, c.status);
}

// Fetch transcript segments for a call
const segments = await identity.listTranscripts(calls[0].id);
for (const t of segments) {
  console.log(`[${t.party}] ${t.text}`);  // party: "local" or "remote"
}

// Read transcripts across all recent calls
const recentCalls = await identity.listCalls({ limit: 10 });
for (const call of recentCalls) {
  const segs = await identity.listTranscripts(call.id);
  if (!segs.length) continue;
  console.log(`\n--- Call ${call.id} (${call.direction}) ---`);
  for (const t of segs) {
    console.log(`  [${t.party.padEnd(6)}] ${t.text}`);
  }
}

// Filter to only the remote party's speech
const remoteOnly = segments.filter(t => t.party === "remote");
for (const t of remoteOnly) console.log(t.text);

// Search transcripts across a phone number (org-level)
const hits = await inkbox.phoneNumbers.searchTranscripts(phone.id, { q: "refund", party: "remote" });
for (const t of hits) {
  console.log(`[${t.party}] ${t.text}`);
}

Text Messages (SMS/MMS)

Receive and read inbound text messages. Outbound SMS sending is coming soon.

// List text messages
const texts = await identity.listTexts({ limit: 20 });
for (const t of texts) {
  console.log(t.remotePhoneNumber, t.text, t.isRead);
}

// Filter to unread only
const unread = await identity.listTexts({ isRead: false });

// Get a single text
const text = await identity.getText("text-uuid");
console.log(text.type);  // "sms" or "mms"
if (text.media) {         // MMS attachments (presigned S3 URLs, 1hr expiry)
  for (const m of text.media) {
    console.log(m.contentType, m.size, m.url);
  }
}

// List conversation summaries (one row per remote number)
const convos = await identity.listTextConversations({ limit: 20 });
for (const c of convos) {
  console.log(c.remotePhoneNumber, c.latestText, c.unreadCount);
}

// Get messages in a specific conversation
const msgs = await identity.getTextConversation("+15167251294", { limit: 50 });

// Mark as read
await identity.markTextRead("text-uuid");
await identity.markTextConversationRead("+15167251294");

// Org-level: search and delete
const results = await inkbox.texts.search(phone.id, { q: "invoice", limit: 20 });
await inkbox.texts.update(phone.id, "text-uuid", { status: "deleted" });

Credentials

Access credentials stored in the vault through the agent-facing credentials surface. The vault must be unlocked first.

// Unlock the vault (once per session)
await inkbox.vault.unlock("my-Vault-key-01!");

const identity = await inkbox.getIdentity("my-agent");
const creds = await identity.getCredentials();

// Discovery — list credentials this identity has access to
for (const login of creds.listLogins()) {
  console.log(login.name, (login.payload as LoginPayload).username);
}

for (const key of creds.listApiKeys()) {
  console.log(key.name, (key.payload as APIKeyPayload).accessKey);
}

// Access by UUID — returns the typed payload directly
const login  = creds.getLogin("secret-uuid");    // → LoginPayload
const apiKey = creds.getApiKey("secret-uuid");    // → APIKeyPayload
const sshKey = creds.getSshKey("secret-uuid");    // → SSHKeyPayload

// Generic access
const secret = creds.get("secret-uuid");          // → DecryptedVaultSecret

Vault Management

Manage the encrypted vault at the org level. Access via inkbox.vault.

// Get vault metadata (key counts, secret counts)
const info = await inkbox.vault.info();
console.log(info.secretCount, info.keyCount);

// Initialize a new vault (creates primary key + recovery keys)
const result = await inkbox.vault.initialize("my-Vault-key-01!");
for (const key of result.recoveryKeys) {
  console.log(key.recoveryCode); // save these immediately
}

// Rotate the vault password
await inkbox.vault.updateKey({
  newVaultKey: "new-Vault-key-02!",
  currentVaultKey: "my-Vault-key-01!",
});

// Rotate using a recovery code (if primary key is lost)
await inkbox.vault.updateKey({
  newVaultKey: "new-Vault-key-02!",
  recoveryCode: "recovery-code-here",
});

// List vault keys
const keys = await inkbox.vault.listKeys();                          // all keys
const primaryKeys = await inkbox.vault.listKeys({ keyType: "PRIMARY" });
const recoveryKeys = await inkbox.vault.listKeys({ keyType: "RECOVERY" });

// List secrets (metadata only — no encrypted payloads)
const secrets = await inkbox.vault.listSecrets();
const logins  = await inkbox.vault.listSecrets({ secretType: "login" });

// Delete a secret
await inkbox.vault.deleteSecret("secret-uuid");

// Unlock the vault for decryption (returns an UnlockedVault)
const unlocked = await inkbox.vault.unlock("my-Vault-key-01!");
const secret = await unlocked.getSecret("secret-uuid");
console.log(secret.name, secret.payload);

Access control

Control which identities can access which secrets.

// List access rules for a secret
const rules = await inkbox.vault.listAccessRules("secret-uuid");
for (const rule of rules) {
  console.log(rule.identityId);
}

// Grant an identity access to a secret
await inkbox.vault.grantAccess("secret-uuid", "identity-uuid");

// Revoke access
await inkbox.vault.revokeAccess("secret-uuid", "identity-uuid");

Identity Secret Management

Manage vault secrets scoped to a specific identity. These methods create secrets and automatically grant the identity access.

const identity = await inkbox.getIdentity("my-agent");

// Create a secret and auto-grant this identity access
const secret = await identity.createSecret({
  name: "CRM Login",
  payload: { type: "login", username: "[email protected]", password: "s3cret" },
  description: "CRM service account",
});

// Fetch and decrypt a secret
const decrypted = await identity.getSecret(secret.id);
console.log(decrypted.payload);

// Delete a secret
await identity.deleteSecret(secret.id);

// Revoke this identity's access (without deleting the secret)
await identity.revokeCredentialAccess(secret.id);

TOTP (one-time passwords)

Add, remove, and generate TOTP codes for login secrets.

// Add TOTP to a login secret (accepts otpauth:// URI or TOTPConfig)
await identity.setTotp(secret.id, "otpauth://totp/Example:user?secret=JBSWY3DPEHPK3PXP&issuer=Example");

// Generate the current TOTP code
const code = await identity.getTotpCode(secret.id);
console.log(code.code, code.expiresIn);

// Remove TOTP from a secret
await identity.removeTotp(secret.id);

Org-level Messages and Threads

Access messages and threads directly without going through an identity. Useful for org-wide operations.

// List messages for a mailbox (paginated automatically)
for await (const msg of inkbox.messages.list("[email protected]")) {
  console.log(msg.subject);
}

// Get a single message with full body
const detail = await inkbox.messages.get("[email protected]", "message-uuid");
console.log(detail.bodyText);

// Send a message from a mailbox
await inkbox.messages.send("[email protected]", {
  to: ["[email protected]"],
  subject: "Hello",
  bodyText: "Hi there!",
});

// Update message flags
await inkbox.messages.updateFlags("[email protected]", "message-uuid", { isRead: true });
await inkbox.messages.markRead("[email protected]", "message-uuid");
await inkbox.messages.markUnread("[email protected]", "message-uuid");
await inkbox.messages.star("[email protected]", "message-uuid");
await inkbox.messages.unstar("[email protected]", "message-uuid");

// Delete a message
await inkbox.messages.delete("[email protected]", "message-uuid");

// Get an attachment presigned URL
const attachment = await inkbox.messages.getAttachment("[email protected]", "message-uuid", "report.pdf");
console.log(attachment.url);

// List threads (paginated automatically)
for await (const thread of inkbox.threads.list("[email protected]")) {
  console.log(thread.subject, thread.messageCount);
}

// Get a thread with all messages
const thread = await inkbox.threads.get("[email protected]", "thread-uuid");

// Delete a thread
await inkbox.threads.delete("[email protected]", "thread-uuid");

Org-level Calls and Transcripts

Access calls and transcripts directly. Access via inkbox.calls and inkbox.transcripts.

// List calls for a phone number
const calls = await inkbox.calls.list("phone-number-uuid", { limit: 10 });
for (const call of calls) {
  console.log(call.id, call.direction, call.status);
}

// Get a single call
const call = await inkbox.calls.get("phone-number-uuid", "call-uuid");

// Place an outbound call
const placed = await inkbox.calls.place({
  fromNumber: "phone-number-uuid",
  toNumber: "+15167251294",
  clientWebsocketUrl: "wss://example.com/ws",
});

// List transcript segments for a call
const segments = await inkbox.transcripts.list("phone-number-uuid", "call-uuid");
for (const t of segments) {
  console.log(`[${t.party}] ${t.text}`);
}

Org-level Mailboxes

Manage mailboxes directly without going through an identity. Access via inkbox.mailboxes.

// List all mailboxes in the organisation
const mailboxes = await inkbox.mailboxes.list();

// Get a specific mailbox
const mailbox = await inkbox.mailboxes.get("[email protected]");

// Create a mailbox linked to an agent identity
const mb = await inkbox.mailboxes.create({
  agentHandle: "support-agent",
  displayName: "Support Inbox",
});
console.log(mb.emailAddress);

// Update display name or webhook URL
await inkbox.mailboxes.update(mb.emailAddress, { displayName: "New Name" });
await inkbox.mailboxes.update(mb.emailAddress, { webhookUrl: "https://example.com/hook" });
await inkbox.mailboxes.update(mb.emailAddress, { webhookUrl: null }); // remove webhook

// Full-text search across messages in a mailbox
const results = await inkbox.mailboxes.search(mb.emailAddress, { q: "invoice", limit: 20 });
for (const msg of results) {
  console.log(msg.subject, msg.fromAddress);
}

// Delete a mailbox
await inkbox.mailboxes.delete(mb.emailAddress);

Org-level Phone Numbers

Manage phone numbers directly without going through an identity. Access via inkbox.phoneNumbers.

// List all phone numbers in the organisation
const numbers = await inkbox.phoneNumbers.list();

// Get a specific phone number by ID
const number = await inkbox.phoneNumbers.get("phone-number-uuid");

// Provision a new number
const num   = await inkbox.phoneNumbers.provision({ type: "toll_free" });
const local = await inkbox.phoneNumbers.provision({ type: "local", state: "NY" });

// Update incoming call behaviour
await inkbox.phoneNumbers.update(num.id, {
  incomingCallAction: "webhook",
  incomingCallWebhookUrl: "https://example.com/calls",
});
await inkbox.phoneNumbers.update(num.id, {
  incomingCallAction: "auto_accept",
  clientWebsocketUrl: "wss://example.com/ws",
});

// Full-text search across transcripts
const hits = await inkbox.phoneNumbers.searchTranscripts(num.id, { q: "refund", party: "remote" });
for (const t of hits) {
  console.log(`[${t.party}] ${t.text}`);
}

// Release a number
await inkbox.phoneNumbers.release(num.id);

Webhooks

Webhooks are configured on the mailbox or phone number resource — no separate registration step.

Mailbox webhooks

Set a URL on a mailbox to receive message.received and message.sent events.

// Set webhook
await inkbox.mailboxes.update("[email protected]", { webhookUrl: "https://example.com/hook" });

// Remove webhook
await inkbox.mailboxes.update("[email protected]", { webhookUrl: null });

Phone webhooks

Set an incoming call webhook URL and action on a phone number.

// Route incoming calls to a webhook
await inkbox.phoneNumbers.update(number.id, {
  incomingCallAction: "webhook",
  incomingCallWebhookUrl: "https://example.com/calls",
});

Whoami

// Check the authenticated caller's identity
const info = await inkbox.whoami();
console.log(info.authType);        // "api_key" or "jwt"
console.log(info.organizationId);

// Narrow by auth type (discriminated union)
if (info.authType === "api_key") {
  console.log(info.keyId, info.label);
} else {
  console.log(info.email, info.orgRole);
}

Signing Keys

// Create or rotate the org-level webhook signing key (plaintext returned once)
const key = await inkbox.createSigningKey();
console.log(key.signingKey); // save this immediately

Verifying Webhook Signatures

Use verifyWebhook to confirm that an incoming request was sent by Inkbox.

import { verifyWebhook } from "@inkbox/sdk";

// Express — use express.raw() to get the raw body Buffer
app.post("/hooks/mail", express.raw({ type: "*/*" }), (req, res) => {
  const valid = verifyWebhook({
    payload: req.body,
    headers: req.headers,
    secret: "whsec_...",
  });
  if (!valid) return res.status(403).end();
  // handle event ...
});

Examples

Runnable example scripts are available in the examples/typescript directory:

| Script | What it demonstrates | |---|---| | register-agent-identity.ts | Create an identity, assign mailbox + phone number | | agent-send-email.ts | Send an email and a threaded reply | | read-agent-messages.ts | List messages and threads | | create-agent-mailbox.ts | Create, update, search, and delete a mailbox | | create-agent-phone-number.ts | Provision, update, and release a number | | list-agent-phone-numbers.ts | List all phone numbers in the org | | read-agent-calls.ts | List calls and print transcripts | | receive-agent-email-webhook.ts | Register and delete a mailbox webhook | | receive-agent-call-webhook.ts | Register, update, and delete a phone webhook |

License

MIT