# TypeScript SDK

Source: https://agentboxd.com/docs/typescript-sdk

> A zero-dependency typed client for Node 20+.

shell:

```bash
npm install agentboxd
```

The TypeScript client is the npm package `agentboxd`. It has no dependencies: it uses the global `fetch` and `node:crypto`, and needs Node 20 or newer. It ships ES module and CommonJS builds with type declarations. With no options, the client reads `AGENTBOXD_API_KEY` and `AGENTBOXD_BASE_URL` (default `https://api.agentboxd.com`) from the environment.

client.ts:

```ts
import { Agentboxd } from 'agentboxd';

const mr = new Agentboxd(); // or new Agentboxd({ apiKey: 'mr_...', baseUrl: 'https://api.agentboxd.com' })
```

## Methods

| Call | Endpoint |
| --- | --- |
| `mr.inboxes.create({ username?, display_name?, client_id? })` | `POST /v1/inboxes` |
| `mr.inboxes.list()` · `get(id)` · `delete(id)` | `/v1/inboxes` |
| `mr.inboxes.createTemporary({ ttlSeconds })` | `POST /v1/inboxes` with `ttl_seconds` |
| `mr.inboxes.pause(id, { reason? })` · `resume(id)` | `POST /v1/inboxes/:id/pause` · `/resume` (the kill switch) |
| `mr.emergencyStop({ reason? })` | `POST /v1/emergency-stop` ([emergency stop](https://agentboxd.com/docs/emergency-stop) of the whole workspace) |
| `mr.escalation.get()` · `update({ contacts?, triggers?, … })` | `GET` · `PUT /v1/escalation` ([human on call](https://agentboxd.com/docs/human-on-call)) |
| `mr.escalation.inbox.get(id)` · `mr.escalation.inbox.update(id, { override?, contacts?, triggers? })` | `GET` · `PUT /v1/inboxes/:id/escalation` |
| `mr.domains.create/list/get/verify/update/delete` | `/v1/domains` |
| `mr.domains.rotateDkim(id)` · `activateDkim(id)` | `POST /v1/domains/:id/dkim/rotate` · `/activate` |
| `mr.domains.zoneFile(id)` | `GET /v1/domains/:id/zone-file` |
| `mr.messages.send(inboxId, { to, subject, text?, html?, attachments?, data?, type? }, { idempotencyKey? })` | `POST …/messages/send` (`data`/`type`: [agent messaging](https://agentboxd.com/docs/agent-messaging); `send<T>` types `data`) |
| `mr.messages.reply(inboxId, messageId, { text?, html?, reply_all?, data?, type? })` | `POST …/reply` |
| `mr.messages.forward(inboxId, messageId, { to, cc?, bcc?, text?, include_attachments? })` | `POST …/forward` |
| `mr.messages.batchGet(ids)` · `batchUpdate(ids, { add_labels?, remove_labels?, is_read? })` | `POST /v1/messages/batch-get` · `/batch-update` |
| `mr.messages.list(inboxId, { labels?, is_read?, direction?, channel?, type? })` | `GET …/messages` |
| `mr.messages.wait(inboxId, { timeout?, since?, from?, subject?, channel?, type? })` | `GET …/messages/wait` |
| `mr.messages.waitForVerification(inboxId, { timeout?, since?, from?, include_unauthenticated? })` | `GET …/verification` |
| `mr.messages.get(id)` · `update(id, { add_labels?, remove_labels?, is_read? })` | `/v1/messages/:id` |
| `mr.messages.claim(inboxId, { limit?, lease_seconds?, consumer?, wait?, enriched?, analysis?, since? })` | `POST …/messages/claim` ([claim/ack queue](https://agentboxd.com/docs/api#claim-and-ack-work-queue)) |
| `mr.messages.ack(id, leaseId, { mark_read? })` · `nack(id, leaseId, { delay_seconds? })` · `extend(id, leaseId, { lease_seconds? })` | `POST /v1/messages/:id/ack` · `/nack` · `/extend` |
| `mr.inboxes.consume(inboxId, handler, { concurrency?, leaseSeconds?, signal?, onError? })` | The consumer loop: claims, extends the lease while `handler(message, ctx)` runs, acks when it returns, nacks with backoff when it throws. Runs until `signal` aborts |
| `mr.threads.list(inboxId, filters?)` · `listAll({ inbox_id?, labels?, before?, after?, participant?, subject?, ascending? })` · `get(id)` · `delete(id)` | `/v1/threads` · `/v1/inboxes/:id/threads` |
| `mr.search(q, { inbox_id? })` | `GET /v1/search` |
| `mr.inboxes.update(id, { display_name?, metadata? })` | `PATCH /v1/inboxes/:id` |
| `mr.threads.update(id, { metadata?, add_labels?, remove_labels? })` | `PATCH /v1/threads/:id` |
| `mr.contacts.list({ q?, label?, metadata? })` · `get(id)` · `byAddress(address)` | `/v1/contacts` |
| `mr.contacts.update(id, { name?, notes?, metadata?, add_labels?, remove_labels? })` | `PATCH /v1/contacts/:id` |
| `mr.contacts.delete(id)` | `DELETE /v1/contacts/:id` |
| `mr.knowledge.list/create/get/update/delete` | `/v1/knowledge` |
| `mr.knowledge.search(q, { inbox_id?, limit? })` | `GET /v1/knowledge/search` |
| `mr.messages.draftReply(id, { instructions?, save? })` | `POST /v1/messages/:id/draft-reply` |
| `mr.messages.attachmentText(messageId, attachmentId, { offset?, maxChars? })` | `GET /v1/messages/:id/attachments/:attachmentId/text` ([Document extraction](https://agentboxd.com/docs/document-extraction)) |
| `mr.messages.extractAttachment(messageId, attachmentId, { schema, instructions? })` | `POST /v1/messages/:id/attachments/:attachmentId/extract` |
| `mr.drafts.create(inboxId, { to?, subject?, text?, reply_to_message_id?, send_at? })` · `list` · `listAll` (org-wide) · `get` · `update` · `delete` | `/v1/inboxes/:id/drafts` ([Drafts](https://agentboxd.com/docs/drafts)) |
| `mr.drafts.send(inboxId, draftId)` · `schedule(inboxId, draftId, sendAt)` · `cancel(inboxId, draftId)` | `POST …/drafts/:draftId/send` · `/schedule` · `/cancel` |
| `mr.webhooks.create/list/get/update/delete/test` | `/v1/webhooks` |
| `mr.deliverability()` | `GET /v1/deliverability` |
| `mr.stream({ inboxIds?, eventTypes?, payload?, since? })` | `GET /v1/stream` (WebSocket; async iterator, reconnects and resumes) |
| `mr.streamToken()` | `POST /v1/stream/token` |
| `mr.identity.token({ inboxId \| identityId, audience, nonce?, scope?, expiresIn? })` | `POST /v1/inboxes/:id/identity-token` ([Sign in with Agentboxd](https://agentboxd.com/docs/agent-identity)) |
| `mr.identities.create/list/get/update/delete` · `pause(id)` · `resume(id)` | `/v1/identities` ([identities without a mailbox](https://agentboxd.com/docs/agent-identity#identity-without-a-mailbox)) |
| `mr.identity.clients.list/create/get/update/delete/rotateSecret` | `/v1/identity/clients` |
| `mr.identity.inbox.get(inboxId)` · `update(inboxId, { enabled })` · `signIns(inboxId)` | `/v1/inboxes/:id/identity` |
| `verifyAgentIdentityToken(token, { audience, nonce?, replayCache? })` | For apps: verifies an agent’s ID token against the JWKS (from `agentboxd/identity`; needs `jose`) |
| `verifyAgentMessage(message, { recipient, replayCache?, maxAgeSeconds? })` | Verifies an [agent message](https://agentboxd.com/docs/agent-messaging#verify-a-message-outside-agentboxd) signature against `agent-keys.json` (from `agentboxd/identity`; needs `jose`, and `canonicalize` for messages with data) |

## Work queue

Delivery is at-least-once: a message whose handler crashed the process comes back after its lease, with a higher `delivery_count`. Make the handler idempotent. `ctx.signal` aborts if the lease is lost.

worker.ts:

```ts
import { Agentboxd } from 'agentboxd';

const mr = new Agentboxd();
const stop = new AbortController();
process.on('SIGTERM', () => stop.abort()); // finish running work, then exit

await mr.inboxes.consume(
  inboxId,
  async (message, ctx) => {
    await handleTicket(message, { signal: ctx.signal }); // acked when this returns, nacked if it throws
  },
  { concurrency: 4, enriched: true, signal: stop.signal },
);
```

## Contacts and knowledge

contacts.ts:

```ts
// Who is this? Name, notes, metadata and labels, created from mail.
const c = await mr.contacts.byAddress('priya.n@gmail.com');
console.log(c.notes, c.metadata.crm_id); // "Wholesale…" "hs_48213"

// Link it to your CRM and remember something. Metadata merges; null deletes a key.
await mr.contacts.update(c.id, {
  notes: `${c.notes ?? ''}
Asked about order 1042 on 25 Sep.`,
  metadata: { crm_id: 'hs_48213', plan: 'wholesale' },
  add_labels: ['customer'],
});

// Find contacts by your own id.
const { data } = await mr.contacts.list({ metadata: { crm_id: 'hs_48213' } });

```

knowledge.ts:

```ts
// Workspace-wide (no inbox_id) or for one inbox.
await mr.knowledge.create({
  title: 'Shipping times',
  body: '- Germany: 1–2 working days (DHL)
- EU: 3–5 working days',
  inbox_id: inbox.id,
});

// Ranked full-text search. With inbox_id, workspace-wide documents are included too.
const { data: hits } = await mr.knowledge.search('how long does shipping to France take', { inbox_id: inbox.id });
// [{ id, title: 'Shipping times', inbox_id, rank: 0.61, snippet: '…EU: 3–5 working days…' }]

```

## Errors

Non-2xx responses throw `AgentboxdError` (also exported as `MailroomError`) with `status`, `code` and `message` from the API’s error body.

errors.ts:

```ts
import { AgentboxdError } from 'agentboxd';

try {
  await mr.messages.send(inbox.id, { to: 'x@example.com', subject: 'Hi', text: '…' });
} catch (e) {
  if (e instanceof AgentboxdError && e.code === 'daily_send_limit_exceeded') {
    // back off until 00:00 UTC
  }
}
```

## Webhook verification

`verifyWebhook(signature, timestamp, rawBody, secret, { toleranceSeconds? })` returns a boolean. See [Webhooks](https://agentboxd.com/docs/webhooks) for a full Express example.
