{"_id":"@centerfy/sdk","_rev":"6-3c4c5b73014f01ba25e47042f95e23e6","name":"@centerfy/sdk","dist-tags":{"latest":"1.0.5"},"versions":{"1.0.0":{"name":"@centerfy/sdk","version":"1.0.0","keywords":["centerfy","crm","sdk","api","ai","typescript","voice-agents"],"author":{"name":"Centerfy AI"},"license":"MIT","_id":"@centerfy/sdk@1.0.0","maintainers":[{"name":"centerfy","email":"dev@centerfy.ai"}],"homepage":"https://centerfy.ai","bugs":{"url":"https://github.com/centerfy/centerfy-typescript-sdk/issues"},"bin":{"centerfy":"dist/cli.cjs"},"dist":{"shasum":"89534826f254d1e5ceb393289986c195799f3a04","tarball":"https://registry.npmjs.org/@centerfy/sdk/-/sdk-1.0.0.tgz","fileCount":10,"integrity":"sha512-IbHV8yqvW+oXctrMTMZeoW48xcJkhOZqClWQBpALxPPiTXeS9ofONoxPBQusnhk/rXSbhAbR7xbVlI1P0qQEJw==","signatures":[{"sig":"MEQCIF82pgWcdAMlzqXVOTn2H4KKxKXdCi72xFBRGHZZVfHtAiA/MIuu/e3q61r0qjLhMmhiddc6tagskeQn9vAHhKI6DQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":588892},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"7d751e6187d9c913aeee188544cdea042370ea68","scripts":{"dev":"tsup --watch","lint":"tsc --noEmit","build":"tsup","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"centerfy","email":"dev@centerfy.ai"},"repository":{"url":"git+https://github.com/centerfy/centerfy-typescript-sdk.git","type":"git"},"_npmVersion":"11.12.1","description":"Official TypeScript SDK for the Centerfy AI API (inbound webhook endpoints).","directories":{},"_nodeVersion":"25.9.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","typescript":"^5.7.2","@types/node":"^22.19.21"},"_npmOperationalInternal":{"tmp":"tmp/sdk_1.0.0_1781526770255_0.6512457722570049","host":"s3://npm-registry-packages-npm-production"}},"1.0.1":{"name":"@centerfy/sdk","version":"1.0.1","keywords":["centerfy","crm","sdk","api","ai","typescript","voice-agents"],"author":{"name":"Centerfy AI"},"license":"MIT","_id":"@centerfy/sdk@1.0.1","maintainers":[{"name":"centerfy","email":"dev@centerfy.ai"}],"homepage":"https://centerfy.ai","bugs":{"url":"https://github.com/centerfy/centerfy-typescript-sdk/issues"},"bin":{"centerfy":"dist/cli.cjs"},"dist":{"shasum":"42b154320327963e5d4e98d52bf8d78b273dbf19","tarball":"https://registry.npmjs.org/@centerfy/sdk/-/sdk-1.0.1.tgz","fileCount":10,"integrity":"sha512-gew33GS5eab7nR6Ffk+jAPQ8jvjNK064X6LgSGBCZr5uVKuWhQ6qeVn2bAi8zQj+SYXEfqAl1k5J/sndxXJrDQ==","signatures":[{"sig":"MEYCIQDuM34IeIaT++JTgZA/QXAhayfhiSxHybMUywj4BC9tNAIhALbSDsGdndDqzSY5ctl1ElQmsBTaSgNCdR0vcs+55ltD","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":595074},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"7d751e6187d9c913aeee188544cdea042370ea68","scripts":{"dev":"tsup --watch","lint":"tsc --noEmit","build":"tsup","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"centerfy","email":"dev@centerfy.ai"},"repository":{"url":"git+https://github.com/centerfy/centerfy-typescript-sdk.git","type":"git"},"_npmVersion":"11.12.1","description":"Official TypeScript SDK for the Centerfy AI API (inbound webhook endpoints).","directories":{},"_nodeVersion":"25.9.0","_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","typescript":"^5.7.2","@types/node":"^22.19.21"},"_npmOperationalInternal":{"tmp":"tmp/sdk_1.0.1_1781527181281_0.2193325739850145","host":"s3://npm-registry-packages-npm-production"}},"1.0.2":{"name":"@centerfy/sdk","version":"1.0.2","keywords":["centerfy","crm","sdk","api","ai","typescript","voice-agents"],"author":{"name":"Centerfy AI"},"license":"MIT","_id":"@centerfy/sdk@1.0.2","maintainers":[{"name":"centerfy","email":"dev@centerfy.ai"}],"homepage":"https://centerfy.ai","bugs":{"url":"https://github.com/Centerfy-Ltd/centerfyai-ts/issues"},"dist":{"shasum":"93c1a77ec7b4b1fd533993c0f44b011059e1d461","tarball":"https://registry.npmjs.org/@centerfy/sdk/-/sdk-1.0.2.tgz","fileCount":9,"integrity":"sha512-U86IdVgfFzAiV19F3Km3MKP3m0Ddjh+DDzd2tZqM3r45fOOrWRXxS+osHnB4K9seiEC955cFjP3U3txXDi4WUg==","signatures":[{"sig":"MEUCICcRoPj5nN9HJGn3DcWiXn9I7smU9n0RdRs6qTCmAftAAiEAhdB9RWTDFog/A2428CCZIdz4KtFYppwnao6LA26WuGI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":535133},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"ca1a8431d085d62fdaf2d12e2c6d89c3fc195606","scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"centerfy","email":"dev@centerfy.ai"},"repository":{"url":"git+https://github.com/Centerfy-Ltd/centerfyai-ts.git","type":"git","directory":"packages/sdk"},"_npmVersion":"11.12.1","description":"Official TypeScript SDK for the Centerfy AI API.","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","typescript":"^5.7.2","@types/node":"^22.19.21"},"_npmOperationalInternal":{"tmp":"tmp/sdk_1.0.2_1781528455423_0.6828568254515268","host":"s3://npm-registry-packages-npm-production"}},"1.0.3":{"name":"@centerfy/sdk","version":"1.0.3","keywords":["centerfy","crm","sdk","api","ai","typescript","voice-agents"],"author":{"name":"Centerfy AI"},"license":"MIT","_id":"@centerfy/sdk@1.0.3","maintainers":[{"name":"centerfy","email":"dev@centerfy.ai"}],"homepage":"https://centerfy.ai","bugs":{"url":"https://github.com/Centerfy-Ltd/centerfyai-ts/issues"},"dist":{"shasum":"09d949b401eab5bb2c7198d5cbf9d3043b57a516","tarball":"https://registry.npmjs.org/@centerfy/sdk/-/sdk-1.0.3.tgz","fileCount":9,"integrity":"sha512-17bPqig/vI52INwdEm3fKywa7GjSlgZkCgn9wriwE5LRH8rfrl1xn8AnlhZedEmg9Bqy/dxKeG6DEEdSSqkrEA==","signatures":[{"sig":"MEUCIGk7LdwCynwkMA/P8qqvNZwxiucLMeutuLKDwSgCSQLeAiEA2jXlpJ3G6yI018NRWD6QKSKK7iCvc0jUPSZ1iCNQlRM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":530985},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"ca1a8431d085d62fdaf2d12e2c6d89c3fc195606","scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"centerfy","email":"dev@centerfy.ai"},"repository":{"url":"git+https://github.com/Centerfy-Ltd/centerfyai-ts.git","type":"git","directory":"packages/sdk"},"_npmVersion":"11.12.1","description":"Official TypeScript SDK for the Centerfy AI API.","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","typescript":"^5.7.2","@types/node":"^22.19.21"},"_npmOperationalInternal":{"tmp":"tmp/sdk_1.0.3_1781528739522_0.8675275673021774","host":"s3://npm-registry-packages-npm-production"}},"1.0.4":{"name":"@centerfy/sdk","version":"1.0.4","keywords":["centerfy","crm","sdk","api","ai","typescript","voice-agents"],"author":{"name":"Centerfy AI"},"license":"MIT","_id":"@centerfy/sdk@1.0.4","maintainers":[{"name":"centerfy","email":"dev@centerfy.ai"}],"homepage":"https://centerfy.ai","dist":{"shasum":"adfdb7170e4e82572f0b386965fd978a5e5a21be","tarball":"https://registry.npmjs.org/@centerfy/sdk/-/sdk-1.0.4.tgz","fileCount":9,"integrity":"sha512-sUtiexXmrPiDViXerLPYnzkmFRyHXjtlig8APwcjFsgVhioH/NEOdTgexuZvboOioe1HA9XbEKa5gs2XNwLZlw==","signatures":[{"sig":"MEYCIQCyWwjh/u5mBHNXV9npqWUZxof1NbaHq9Bsa8e3jdmMpAIhAIXYQ9irizAA7V/X/au6DMkALWFItQKxPDt2+C/jM+HR","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":530290},"main":"./dist/index.cjs","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"gitHead":"ca1a8431d085d62fdaf2d12e2c6d89c3fc195606","scripts":{"dev":"tsup --watch","build":"tsup","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"_npmUser":{"name":"centerfy","email":"dev@centerfy.ai"},"_npmVersion":"11.12.1","description":"Official TypeScript SDK for the Centerfy AI API.","directories":{},"_nodeVersion":"25.9.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.3.5","typescript":"^5.7.2","@types/node":"^22.19.21"},"_npmOperationalInternal":{"tmp":"tmp/sdk_1.0.4_1781529144676_0.7945898734155812","host":"s3://npm-registry-packages-npm-production"}},"1.0.5":{"name":"@centerfy/sdk","version":"1.0.5","description":"Official TypeScript SDK for the Centerfy AI API.","license":"MIT","author":{"name":"Centerfy AI"},"homepage":"https://centerfy.ai","keywords":["centerfy","crm","sdk","api","ai","typescript","voice-agents"],"type":"module","main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"}},"engines":{"node":">=18"},"scripts":{"build":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"publishConfig":{"access":"public"},"devDependencies":{"@types/node":"^22.19.21","tsup":"^8.3.5","typescript":"^5.7.2"},"gitHead":"54dd60e1d7db6626f88171486d3c27d3654caa10","_id":"@centerfy/sdk@1.0.5","_nodeVersion":"25.9.0","_npmVersion":"11.12.1","dist":{"integrity":"sha512-y2yRTvK4kh516HUnT/ZxjILw/tDHDfga2we+/zHgKtbatLO7nAaRFmayhAMSE9YWZmdHNRTBeZcWPEuSLl/NUQ==","shasum":"f8b0629d838a5207cc067591060d38f3b0d30525","tarball":"https://registry.npmjs.org/@centerfy/sdk/-/sdk-1.0.5.tgz","fileCount":9,"unpackedSize":530786,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDHabfFVdBUu35bbIORFQUndWd5CLndzkK7BiqcW3EtwAIhALiWjA+EWSul4JPyywE3yAL66sJ0lAol1RcVKDGfzIl0"}]},"_npmUser":{"name":"centerfy","email":"dev@centerfy.ai"},"directories":{},"maintainers":[{"name":"centerfy","email":"dev@centerfy.ai"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/sdk_1.0.5_1781545870493_0.22565222547810215"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-15T12:32:50.094Z","modified":"2026-06-15T17:51:10.738Z","1.0.0":"2026-06-15T12:32:50.400Z","1.0.1":"2026-06-15T12:39:41.431Z","1.0.2":"2026-06-15T13:00:55.591Z","1.0.3":"2026-06-15T13:05:39.677Z","1.0.4":"2026-06-15T13:12:24.827Z","1.0.5":"2026-06-15T17:51:10.620Z"},"author":{"name":"Centerfy AI"},"license":"MIT","homepage":"https://centerfy.ai","keywords":["centerfy","crm","sdk","api","ai","typescript","voice-agents"],"description":"Official TypeScript SDK for the Centerfy AI API.","maintainers":[{"name":"centerfy","email":"dev@centerfy.ai"}],"readme":"# Centerfy TypeScript SDK\n\nThe official TypeScript SDK for the [Centerfy AI](https://centerfy.ai) API. `@centerfy/sdk` gives you fully typed access to your Centerfy sub-account — CRM contacts, omnichannel messaging (email, SMS, WhatsApp, Facebook, Instagram), AI voice agents, knowledge bases, appointments, phone numbers, conversations, and call history — from Node.js or any JavaScript runtime with `fetch`. Every method, parameter, and response is typed, and every failure throws a typed error you can branch on with `instanceof`.\n\n## Installation\n\n```sh\nnpm install @centerfy/sdk\n# or\nyarn add @centerfy/sdk\n# or\npnpm add @centerfy/sdk\n```\n\nRequires Node.js 18+ (which ships `fetch` natively). The SDK also works in any runtime that provides `fetch` — Deno, Bun, Cloudflare Workers, modern browsers — or you can supply your own implementation via the `fetch` option.\n\n## Quickstart\n\nCreate an API key in **Centerfy → Settings → API & Webhooks**, then:\n\n```ts\nimport { CenterfyClient } from \"@centerfy/sdk\";\n\nconst centerfy = new CenterfyClient({ apiKey: \"cfy_...\" });\n\n// List contacts\nconst { contacts, has_more } = await centerfy.contacts.list({ limit: 25 });\n\n// Create a contact (or update an existing one matched by email/phone)\nconst created = await centerfy.contacts.create({\n  first_name: \"Ada\",\n  last_name: \"Lovelace\",\n  email: \"ada@example.com\",\n  phone: \"+14155550123\",\n});\n\n// Have an AI agent write and send a message to that contact\nconst reply = await centerfy.messages.sendAi({\n  channel: \"sms\",\n  contact_id: created.contact_id,\n  agent_id: \"AGENT_UUID\",\n  prompt: \"Welcome them and offer to book an intro call this week.\",\n});\nconsole.log(reply.generated_message);\n```\n\n> Prefer the command line? The [`@centerfy/cli`](https://www.npmjs.com/package/@centerfy/cli) package provides an interactive `centerfy` shell built on this SDK — `npm i -g @centerfy/cli`.\n\n## Configuration\n\n`new CenterfyClient(options)` accepts:\n\n| Option | Type | Default | Description |\n| --- | --- | --- | --- |\n| `apiKey` | `string` | — (required) | Your sub-account API key (`cfy_...`), sent as the `x-api-key` header on every request. Create and manage keys in Settings → API & Webhooks. |\n| `baseUrl` | `string` | `https://api.centerfy.ai` | Override the API origin. |\n| `timeoutMs` | `number` | `30000` | Per-attempt request timeout in milliseconds. |\n| `maxRetries` | `number` | `0` | Automatic retries for 429 / 5xx responses and network failures. `Retry-After` is honored on 429; otherwise exponential backoff with jitter. |\n| `fetch` | `typeof fetch` | global `fetch` | Custom fetch implementation (useful for testing or older runtimes). |\n| `defaultHeaders` | `Record<string, string>` | `{}` | Extra headers to send with every request. |\n\nEvery SDK method also accepts an optional final `options` argument (`{ signal?: AbortSignal, headers?: Record<string, string> }`) for per-request cancellation and headers.\n\n## Error handling\n\nEvery failed API call throws a subclass of `CenterfyError`, so you can branch on the failure mode without string-matching messages:\n\n```ts\nimport {\n  CenterfyError,\n  CenterfyNotFoundError,\n  CenterfyRateLimitError,\n  CenterfyValidationError,\n} from \"@centerfy/sdk\";\n\ntry {\n  await centerfy.contacts.get(\"missing-id\");\n} catch (err) {\n  if (err instanceof CenterfyNotFoundError) {\n    console.log(\"Contact does not exist\");\n  } else if (err instanceof CenterfyRateLimitError) {\n    console.log(`Rate limited — retry in ${err.retryAfterSeconds}s`);\n  } else if (err instanceof CenterfyValidationError) {\n    console.log(\"Bad request:\", err.message, err.body);\n  } else if (err instanceof CenterfyError) {\n    console.log(`API error ${err.status} on ${err.request?.method} ${err.request?.path}`);\n  } else {\n    throw err;\n  }\n}\n```\n\n| Error class | Status | When |\n| --- | --- | --- |\n| `CenterfyAuthError` | 401 | The API key is missing, malformed, or revoked. |\n| `CenterfyForbiddenError` | 403 | The key is valid but the account is not entitled to API access (plan gate). |\n| `CenterfyNotFoundError` | 404 | The requested resource does not exist. |\n| `CenterfyValidationError` | 400 / 422 | The request payload failed validation. |\n| `CenterfyRateLimitError` | 429 | Rate limit exceeded. Exposes `retryAfterSeconds` from the `Retry-After` header. |\n| `CenterfyServerError` | 5xx | The API encountered an unexpected server-side error. |\n| `CenterfyConnectionError` | — | Network/transport failure (DNS, TLS, timeout, abort) — no HTTP response received. |\n\nAll errors carry `status` (0 for transport failures), the parsed JSON error `body` when available, and the `request` (`{ method, path }`) that failed.\n\n**Rate limits.** The API allows **50 requests per minute per sub-account**. On 429 the response includes a `Retry-After` header; set `maxRetries` on the client to have the SDK automatically retry 429 and 5xx responses, honoring `Retry-After`:\n\n```ts\nconst centerfy = new CenterfyClient({ apiKey: \"cfy_...\", maxRetries: 3 });\n```\n\n## Pagination\n\nList endpoints that page (`contacts.list`, `conversations.list`, `calls.list`) use `limit`/`offset` parameters and return `total` and `has_more`:\n\n```ts\nconst all = [];\nlet offset = 0;\n\nwhile (true) {\n  const page = await centerfy.contacts.list({ limit: 100, offset });\n  all.push(...page.contacts);\n  if (!page.has_more) break;\n  offset += page.limit;\n}\n```\n\n`limit` is 1–100 (default 50; values above 100 are capped) and `offset` defaults to 0.\n\n## API reference\n\nEvery method returns a `Promise` of a fully typed response and accepts an optional trailing `RequestOptions` argument (omitted below for brevity).\n\n### `client.contacts`\n\n| Method | Description |\n| --- | --- |\n| `list(params?)` | List contacts, newest first, paginated (`limit` 1–100, default 50; `offset` default 0). |\n| `get(contactId)` | Get a single contact by id. |\n| `create(params)` | Create a contact — or update in place if one matches by email or phone (`created: false`). Requires `email` or an E.164 `phone`. |\n| `update(contactId, params)` | Partial update; only the fields present are changed. `tags` replaces the entire tag set. |\n| `delete(contactId)` | Permanently delete a contact (409 if still referenced by related records). |\n| `addTags(contactId, params)` | Add (merge) one or more tags; values are lowercased, trimmed and de-duped. |\n| `removeTags(contactId, params)` | Remove one or more tags; matching is case-insensitive, missing tags are ignored. |\n| `createNote(contactId, params)` | Create a note on a contact (`content` 1–5000 characters). |\n| `listNotes(contactId)` | List a contact's active notes, newest first. |\n| `deleteNote(contactId, noteId)` | Delete a contact's note (soft delete). |\n| `makeAiCall(contactId, params)` | Place an outbound AI phone call to the contact's phone on file. Requires `agent_id`; optional `pool_id` round-robins the caller-ID. |\n\n### `client.tags`\n\n| Method | Description |\n| --- | --- |\n| `list()` | List the distinct contact tags in your sub-account, sorted A→Z, each with a usage count. |\n\n### `client.phoneNumbers`\n\n| Method | Description |\n| --- | --- |\n| `list()` | List your owned phone numbers (active only), each with its assigned agent if any. |\n| `get(phoneNumberId)` | Get a single owned phone number by id. |\n| `assignAgent(phoneNumberId, params)` | Assign a number to an agent so its inbound calls route there. Idempotent for the same agent; 409 if assigned to a different agent. |\n| `unassignAgent(phoneNumberId)` | Remove the agent from a number. Idempotent; the number stays active. |\n\n### `client.numberPools`\n\n| Method | Description |\n| --- | --- |\n| `list()` | List your number pools with member numbers resolved, newest first. |\n| `create(params)` | Create an empty number pool (`name` required). |\n| `get(poolId)` | Get a single pool by id, including its member numbers. |\n| `update(poolId, params)` | Rename a pool and/or replace its complete member set (`phone_number_ids` uses set semantics; `[]` clears). |\n| `delete(poolId)` | Delete a pool; its number assignments are removed but the numbers stay in your inventory. |\n| `addNumber(poolId, params)` | Add an owned number to a pool. Idempotent (`already_in_pool: true`). |\n| `removeNumber(poolId, numberId)` | Remove a number from a pool; the number stays in your inventory. |\n\n### `client.voices`\n\n| Method | Description |\n| --- | --- |\n| `list(params?)` | List the AI voices available for agents, optionally filtered by gender. Each voice's `name` is the identifier to use as an agent's `voice_id`. |\n| `preview(params)` | Generate a spoken preview of a voice. Resolves with raw MP3 bytes (`ArrayBuffer`) rather than JSON. |\n\n### `client.agents`\n\n| Method | Description |\n| --- | --- |\n| `create(params)` | Create an AI agent. Core configuration fields are required; conditional fields apply when their toggles are true. |\n| `list(params?)` | List your AI agents, newest first. Pass `active` to filter active/paused. |\n| `get(agentId)` | Get a single AI agent by id. |\n| `update(agentId, params)` | Partial update — send at least one field; omitted fields keep their values. |\n| `duplicate(agentId)` | Duplicate an agent — full configuration, fresh id, \"(copy)\" appended to the name. |\n| `archive(agentId)` | Archive (soft-delete) an agent — deactivated so it stops taking calls; phone and call history remain. Idempotent. |\n| `listCustomTools(agentId)` | List the custom tools assigned to an agent (secrets are never included). |\n| `assignCustomTool(agentId, params)` | Assign a custom tool to an agent. Idempotent (`already_assigned: true`). |\n| `removeCustomTool(agentId, toolId)` | Remove a custom tool from an agent; the tool itself is untouched. |\n| `assignKnowledgeBase(agentId, params)` | Connect a knowledge base to an agent (an agent has at most one — any existing connection is replaced). Optional `max_results` (default 5) and `similarity_threshold` (default 0.30). |\n| `removeKnowledgeBase(agentId, knowledgeBaseId)` | Disconnect a knowledge base from an agent; only the link is removed. |\n\n### `client.agentFolders`\n\n| Method | Description |\n| --- | --- |\n| `create(params)` | Create an agent folder (`name` required; optional hex `color`, default `#3b82f6`). |\n| `list()` | List your agent folders, oldest first. |\n| `update(folderId, params)` | Partial update — `name` and/or `color`, at least one required. |\n| `addAgent(folderId, params)` | Assign an agent to a folder. An agent belongs to at most one folder, so this re-homes it. |\n| `removeAgent(folderId, agentId)` | Remove an agent from a folder, moving it back to the root. |\n| `delete(folderId)` | Delete a folder. The folder must be empty (409 otherwise). |\n\n### `client.customTools`\n\n| Method | Description |\n| --- | --- |\n| `create(params)` | Create a custom tool (`name`, `description`, `url`, `method` required). Created unassigned — attach to an agent separately. |\n| `get(toolId)` | Fetch a custom tool by id. The stored API-key secret is never returned (`has_api_key` indicates whether one is set). |\n| `update(toolId, params)` | Partial update — only the fields you send change; at least one required. |\n| `delete(toolId)` | Delete a custom tool, unlinking it from every agent first (409 for tools registered on the voice engine). |\n\n### `client.knowledgeBases`\n\n| Method | Description |\n| --- | --- |\n| `list()` | List your knowledge bases, newest first, each with source counts. |\n| `create(params)` | Create a knowledge base (`name` required and unique). Optional `chunk_size` (default 1000) and `chunk_overlap` (default 200). |\n| `delete(knowledgeBaseId)` | Delete a knowledge base including its documents and text entries. |\n| `enable(knowledgeBaseId)` | Enable a knowledge base so its agents can retrieve from it. Idempotent. |\n| `disable(knowledgeBaseId)` | Disable a knowledge base; its data is kept. Idempotent. |\n| `addText(knowledgeBaseId, params)` | Add a plain-text source (`title`, `content`; `entry_type` \"text\" or \"snippet\"). Embedded asynchronously. |\n| `addFaq(knowledgeBaseId, params)` | Add an FAQ source (`title` plus non-empty `faq_pairs`); pairs are combined into one entry and embedded asynchronously. |\n| `addUrl(knowledgeBaseId, params)` | Add a URL source; the page is fetched, chunked, and embedded asynchronously. |\n| `addFile(knowledgeBaseId, file, filename?)` | Add a file source via multipart upload (.pdf, .docx, .xlsx, .txt, .csv). xlsx is queued (`processed: false`); the rest process inline. |\n| `deleteSource(knowledgeBaseId, sourceId)` | Delete any source — file/URL or text/FAQ — including its vectors. |\n\n### `client.appointments`\n\n| Method | Description |\n| --- | --- |\n| `list(params)` | List appointments in a date range (`from` and `to` required, ISO 8601), ascending, each with its linked contact. Optional `status` and `timezone`; `limit` default 100, max 500. |\n| `get(appointmentId)` | Fetch a single appointment by id, including its linked contact. |\n| `create(params)` | Create an appointment (`title`, `appointment_date`, `calendar_id` required). The contact is resolved by `contact_id` or by email/phone (created if missing). |\n| `update(appointmentId, params)` | Partial update — only the fields present change. |\n| `delete(appointmentId)` | Permanently delete an appointment (409 if related records prevent deletion). |\n\n### `client.messages`\n\n| Method | Description |\n| --- | --- |\n| `send(params)` | Send a message you wrote to a contact on a channel (email, sms, whatsapp, facebook, instagram). Email requires `subject` plus `message` or `html`. |\n| `sendAi(params)` | Have an AI agent write and send a message to a contact, optionally steered by `prompt`. Returns the `generated_message`. |\n| `listForContact(contactId, params?)` | Fetch a contact's most recent messages across every channel, oldest → newest. Optional `channel` filter; `limit` default 100, max 500. |\n\n### `client.conversations`\n\n| Method | Description |\n| --- | --- |\n| `list(params?)` | List conversations (the inbox), newest first. Optional `channel` and `status` filters; offset paginated. |\n| `get(conversationId)` | Fetch a single conversation by id. |\n| `listMessages(conversationId, params?)` | List a conversation's most recent messages, oldest → newest. Optional `channel` filter; `limit` default 100, max 500. |\n\n### `client.calls`\n\n| Method | Description |\n| --- | --- |\n| `list(params?)` | List call history, newest first, with resolved agent and contact names. Optional `from`/`to`, `status`, `direction`, and `agent_id` filters; offset paginated. |\n| `stats(params?)` | Aggregate call metrics over an optional date range (default: last 30 days) — totals by direction and status, recordings, talk time. |\n| `get(callId)` | Fetch a single call with full detail, including the transcript and AI summary. |\n\n## Examples\n\n### Make an AI call to a contact\n\n```ts\nconst result = await centerfy.contacts.makeAiCall(\"CONTACT_UUID\", {\n  agent_id: \"AGENT_UUID\",\n  pool_id: \"POOL_UUID\", // optional — round-robins the caller-ID\n});\nconsole.log(result.status, result.call_id); // e.g. \"queued\" / \"initiated\"\n```\n\n### Upload a knowledge-base file\n\nNode.js:\n\n```ts\nimport { readFile } from \"node:fs/promises\";\n\nconst bytes = await readFile(\"./pricing.pdf\");\nconst upload = await centerfy.knowledgeBases.addFile(\n  \"KB_UUID\",\n  new Blob([bytes], { type: \"application/pdf\" }),\n  \"pricing.pdf\",\n);\nconsole.log(upload.processed, upload.document.total_chunks);\n```\n\nBrowser:\n\n```ts\nconst file = document.querySelector<HTMLInputElement>(\"#file\")!.files![0];\nconst upload = await centerfy.knowledgeBases.addFile(\"KB_UUID\", file, file.name);\n```\n\n### Save a voice preview MP3\n\n`voices.preview` resolves with the raw audio bytes as an `ArrayBuffer`:\n\n```ts\nimport { writeFile } from \"node:fs/promises\";\n\nconst audio = await centerfy.voices.preview({\n  name: \"Marissa\",\n  text: \"Hi! Thanks for calling — how can I help today?\",\n});\nawait writeFile(\"preview.mp3\", Buffer.from(audio));\n```\n\n### Templated vs AI-generated messages\n\n```ts\n// You write the message\nawait centerfy.messages.send({\n  channel: \"email\",\n  contact_id: \"CONTACT_UUID\",\n  subject: \"Your appointment is confirmed\",\n  message: \"Hi Ada — see you Thursday at 2pm.\",\n});\n\n// The agent writes the message\nconst ai = await centerfy.messages.sendAi({\n  channel: \"whatsapp\",\n  contact_id: \"CONTACT_UUID\",\n  agent_id: \"AGENT_UUID\",\n  prompt: \"Confirm Thursday's appointment and ask if they have questions.\",\n});\nconsole.log(ai.generated_message);\n```\n\n## Project structure\n\nThe SDK source lives under `src/`. Each API resource is its own module, and the\nclient wires them together.\n\n```\nsrc/\n├── index.ts              Public entry point — exports the client, errors, and all types\n├── client.ts             CenterfyClient — groups every operation under a resource namespace\n├── http.ts               Fetch transport — auth header, timeouts, retries, query/body handling\n├── errors.ts             Typed error hierarchy (CenterfyError and subclasses)\n├── resource.ts           Base class shared by every resource (the _get/_post/… helpers)\n├── types.ts              Shared types (pagination, message channels, …)\n└── resources/            One module per API resource — params + response types + methods\n    ├── contacts.ts\n    ├── tags.ts\n    ├── phone-numbers.ts\n    ├── number-pools.ts\n    ├── voices.ts\n    ├── agents.ts\n    ├── agent-folders.ts\n    ├── custom-tools.ts\n    ├── knowledge-bases.ts\n    ├── appointments.ts\n    ├── messages.ts\n    ├── conversations.ts\n    └── calls.ts\n```\n\nA companion command-line interface is published as [`@centerfy/cli`](https://www.npmjs.com/package/@centerfy/cli).\n\n## License\n\nMIT\n","readmeFilename":"README.md"}