@kerabie/sdk 0.0.0-stage → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +344 -3
- package/dist/index.d.mts +1292 -0
- package/dist/index.d.ts +1292 -0
- package/dist/index.js +537 -0
- package/dist/index.mjs +507 -0
- package/package.json +30 -4
package/README.md
CHANGED
|
@@ -1,3 +1,344 @@
|
|
|
1
|
-
#
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
# @kerabie/sdk
|
|
2
|
+
|
|
3
|
+
Official TypeScript SDK for the [Kerabie](https://kerabie.com) customer support platform.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @kerabie/sdk
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick Start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { KerAbieClient } from '@kerabie/sdk';
|
|
15
|
+
|
|
16
|
+
const kerabie = new KerAbieClient({
|
|
17
|
+
apiKey: process.env.KERABIE_API_KEY,
|
|
18
|
+
orgId: 'your-org-id', // Optional; set per request or in client
|
|
19
|
+
baseUrl: 'https://api.kerabie.com', // Default
|
|
20
|
+
});
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Conversations
|
|
24
|
+
|
|
25
|
+
```typescript
|
|
26
|
+
// List conversations
|
|
27
|
+
const { data, meta } = await kerabie.conversations.list({ status: 'OPEN', limit: 20 });
|
|
28
|
+
|
|
29
|
+
// Get a single conversation
|
|
30
|
+
const conv = await kerabie.conversations.get(123);
|
|
31
|
+
|
|
32
|
+
// Send a message
|
|
33
|
+
const msg = await kerabie.conversations.sendMessage(123, { body: 'Hello!' });
|
|
34
|
+
|
|
35
|
+
// Resolve / assign
|
|
36
|
+
await kerabie.conversations.resolve(123);
|
|
37
|
+
await kerabie.conversations.assign(123, agentId);
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Customers
|
|
41
|
+
|
|
42
|
+
```typescript
|
|
43
|
+
// Upsert a customer (create or update)
|
|
44
|
+
const customer = await kerabie.customers.upsert({
|
|
45
|
+
email: 'user@example.com',
|
|
46
|
+
name: 'John Doe',
|
|
47
|
+
externalRef: 'app_user_id_123',
|
|
48
|
+
customAttributes: { plan: 'pro' },
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
// Get conversation history
|
|
52
|
+
const { data: convs } = await kerabie.customers.getConversations(customer.id);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Campaigns
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// Proactive messages on your chat widget
|
|
59
|
+
const { campaigns, meta } = await kerabie.campaigns.list({ status: 'active' });
|
|
60
|
+
|
|
61
|
+
// Saved as a draft; add isActive: true to launch it straight away
|
|
62
|
+
const { campaign } = await kerabie.campaigns.create({
|
|
63
|
+
name: 'Pricing page nudge',
|
|
64
|
+
message: 'Questions about plans? I can compare them for you in two minutes.',
|
|
65
|
+
ctaLabel: 'Compare plans',
|
|
66
|
+
trigger: 'time_on_page',
|
|
67
|
+
triggerValue: 20, // seconds
|
|
68
|
+
pages: '/pricing',
|
|
69
|
+
audience: 'all',
|
|
70
|
+
frequency: 'once_per_visitor',
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
await kerabie.campaigns.update(campaign.id, { isActive: true }); // launch (403 if your plan's live limit is reached)
|
|
74
|
+
await kerabie.campaigns.duplicate(campaign.id); // copy as a draft
|
|
75
|
+
const stats = await kerabie.campaigns.getStats(); // { total, active, sent, opened, clicked, replied, limit, … }
|
|
76
|
+
await kerabie.campaigns.delete(campaign.id);
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`trigger` is `page_load`, `time_on_page` (seconds), `scroll_depth` (percent) or `exit_intent`. `pages` takes comma-separated URL patterns with `*` wildcards; leave it empty for every page. How many campaigns can be live at once depends on your plan (`getStats().limit`, 0 = unlimited).
|
|
80
|
+
|
|
81
|
+
## AI tools
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
// Custom API actions your chatbot can call (check stock, validate an address, book a slot…)
|
|
85
|
+
const { tools, stats } = await kerabie.aiTools.list(); // stats.limit: -1 = not on your plan, 0 = unlimited
|
|
86
|
+
|
|
87
|
+
const { tool } = await kerabie.aiTools.create({
|
|
88
|
+
name: 'check_availability', // snake_case; this is what the AI calls
|
|
89
|
+
displayName: 'Check availability',
|
|
90
|
+
description: 'Use this when a customer asks if a specific item is in stock.',
|
|
91
|
+
endpoint: 'https://api.yourstore.com/inventory/check', // must be a public address
|
|
92
|
+
method: 'GET',
|
|
93
|
+
authType: 'bearer',
|
|
94
|
+
authConfig: { token: process.env.STORE_API_TOKEN }, // write-only, never returned
|
|
95
|
+
responsePath: 'data.message',
|
|
96
|
+
parameters: [
|
|
97
|
+
{ name: 'sku', type: 'string', description: 'Product SKU the customer mentioned' },
|
|
98
|
+
{ name: 'location', type: 'string', description: 'Store, if mentioned', required: false },
|
|
99
|
+
],
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const result = await kerabie.aiTools.test(tool.id, { sku: 'A-100' }); // calls your real endpoint once
|
|
103
|
+
await kerabie.aiTools.update(tool.id, { enabled: false }); // switch it off
|
|
104
|
+
await kerabie.aiTools.duplicate(tool.id); // a copy that starts switched off
|
|
105
|
+
await kerabie.aiTools.delete(tool.id);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
How many tools you can have depends on your plan; `create` and `duplicate` throw a 403 when it is full. Endpoints that resolve to a private or local address are rejected, and redirects are not followed.
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
### MCP servers as tool sources
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// Let your chatbot use the tools of a remote MCP server
|
|
115
|
+
const { server } = await kerabie.mcpServers.create({
|
|
116
|
+
name: 'Shop tools',
|
|
117
|
+
description: 'Order and stock lookups for our shop. Use when a customer asks about an order or an item.',
|
|
118
|
+
url: 'https://mcp.yourstore.com/mcp',
|
|
119
|
+
authType: 'bearer',
|
|
120
|
+
authConfig: { token: process.env.SHOP_MCP_TOKEN },
|
|
121
|
+
});
|
|
122
|
+
console.log(server.tools.map((t) => t.name)); // what the server offers
|
|
123
|
+
|
|
124
|
+
await kerabie.mcpServers.update(server.id, { enabledTools: ['check_stock'] }); // switch tools on
|
|
125
|
+
await kerabie.mcpServers.test(server.id, 'check_stock', { sku: 'A-100' });
|
|
126
|
+
await kerabie.mcpServers.sync(server.id); // read the tools again
|
|
127
|
+
await kerabie.mcpServers.delete(server.id);
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Each switched-on MCP tool counts towards your plan's AI tool limit, the same as your own tools.
|
|
131
|
+
|
|
132
|
+
## Contacts
|
|
133
|
+
|
|
134
|
+
```typescript
|
|
135
|
+
// People you have talked to (or added), last-contacted first
|
|
136
|
+
const { contacts, meta } = await kerabie.contacts.list({ type: 'VIP', search: 'palmex', limit: 20 });
|
|
137
|
+
|
|
138
|
+
const stats = await kerabie.contacts.getStats(); // { total, newThisWeek, leads, vip }
|
|
139
|
+
|
|
140
|
+
// Add one by hand — throws an HttpError with status 409 if the email is already saved
|
|
141
|
+
const { contact } = await kerabie.contacts.create({ name: 'Chidi Okafor', email: 'chidi@palmex.com', company: 'Palmex', contactType: 'CUSTOMER' });
|
|
142
|
+
|
|
143
|
+
await kerabie.contacts.update(contact.id, { contactType: 'VIP', notes: 'Prefers WhatsApp', ownerId: 7 });
|
|
144
|
+
|
|
145
|
+
// Bulk import (max 1,000 per call). Existing emails are skipped, bad rows are reported by number.
|
|
146
|
+
const result = await kerabie.contacts.import([{ name: 'Sam Adeyemi', email: 'sam@tenda.ng', type: 'lead' }]);
|
|
147
|
+
result; // { created, duplicates, invalid: [{ row, error }], total }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
`type` is `LEAD`, `CUSTOMER` or `VIP`. `ownerId` is a team-member id and must belong to your organisation. Emails are stored lower-cased and must be unique per organisation.
|
|
151
|
+
|
|
152
|
+
## Visitors
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
// Who has been on the site recently (online and offline), newest first
|
|
156
|
+
const { data, meta } = await kerabie.visitors.list({ status: 'all', country: 'NG', limit: 20 });
|
|
157
|
+
data[0].visitCount; // 14 → coming back after 30+ minutes away counts as a new visit
|
|
158
|
+
data[0].status; // 'online' | 'offline' | 'blocked' | 'allowed'
|
|
159
|
+
|
|
160
|
+
// On the site right now
|
|
161
|
+
const { data: online } = await kerabie.visitors.getActive();
|
|
162
|
+
|
|
163
|
+
// Online now, countries and returning visitors (24h), blocked IPs, top countries
|
|
164
|
+
const overview = await kerabie.visitors.getOverview();
|
|
165
|
+
|
|
166
|
+
// CSV of the visitors matching the filters (max 5,000 rows) — Pro plan and above
|
|
167
|
+
const { csv, filename, count, truncated } = await kerabie.visitors.export({ status: 'blocked' });
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
`status` comes from your IP rules and wins over presence: an allowlisted IP reads `allowed` even while the visitor is online. `ip` is `null` when your organisation hides visitor IPs in its privacy settings. Visitor history is kept for 30 days. `visitors.export()` needs a plan with data exports; on other plans it throws an `HttpError` with status `403`.
|
|
171
|
+
|
|
172
|
+
## Knowledge Base
|
|
173
|
+
|
|
174
|
+
```typescript
|
|
175
|
+
const { data: articles } = await kerabie.kb.search('refund policy');
|
|
176
|
+
|
|
177
|
+
const stats = await kerabie.kb.getStats(); // { total, published, drafts, views, top, categories }
|
|
178
|
+
const insights = await kerabie.kb.getInsights(); // { mostViewed, gaps, lowestRated, stale }: what to write or fix next
|
|
179
|
+
|
|
180
|
+
const article = await kerabie.kb.createArticle({
|
|
181
|
+
title: 'How to reset your password',
|
|
182
|
+
body: '...markdown content...',
|
|
183
|
+
category: 'account',
|
|
184
|
+
tags: ['password', 'security'],
|
|
185
|
+
});
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Tickets
|
|
189
|
+
|
|
190
|
+
Tickets are async, form-submitted support requests — the same underlying conversation model as live chat, just created through a dedicated flow instead of a real-time widget session. Use `externalRef` to identify your own end users; repeated calls with the same `externalRef` land on the same open ticket instead of creating duplicates.
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// Create a ticket on behalf of one of your users
|
|
194
|
+
const { ticketId } = await kerabie.tickets.create({
|
|
195
|
+
externalRef: 'user_42', // your own stable user id
|
|
196
|
+
subject: 'Refund request',
|
|
197
|
+
message: 'I was charged twice for order #1029.',
|
|
198
|
+
priority: 'HIGH', // LOW | NORMAL | HIGH | URGENT
|
|
199
|
+
name: 'Jane Doe',
|
|
200
|
+
email: 'jane@example.com', // optional — enables email continuation: replies to the notification land back on this ticket
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// List / filter
|
|
204
|
+
const { data: tickets } = await kerabie.tickets.list({ status: 'WAITING', priority: 'HIGH' });
|
|
205
|
+
|
|
206
|
+
// Fetch a ticket with its full message thread
|
|
207
|
+
const ticket = await kerabie.tickets.get(ticketId);
|
|
208
|
+
|
|
209
|
+
// Reply as the end user (e.g. relaying a message from your own app)
|
|
210
|
+
await kerabie.tickets.reply(ticketId, 'Any update on this?');
|
|
211
|
+
|
|
212
|
+
// Update status / priority, or use the shortcuts
|
|
213
|
+
await kerabie.tickets.update(ticketId, { priority: 'URGENT' });
|
|
214
|
+
await kerabie.tickets.resolve(ticketId);
|
|
215
|
+
await kerabie.tickets.close(ticketId);
|
|
216
|
+
|
|
217
|
+
// Overview numbers: open, unassigned, breaching SLA, and 7-day average resolution time
|
|
218
|
+
const stats = await kerabie.tickets.getStats();
|
|
219
|
+
stats.resolutionDeltaPct; // -18 → 18% faster than the 7 days before (null if nothing to compare)
|
|
220
|
+
|
|
221
|
+
// CSV export of the tickets matching the filters (max 5,000 rows)
|
|
222
|
+
const { csv, filename, count, truncated } = await kerabie.tickets.export({ status: 'ACTIVE', priority: 'URGENT' });
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`tickets.export()` needs a plan with data exports (Pro and above); on other plans it throws an `HttpError` with status `403`. Every cell is escaped, and values that start with `=`, `+`, `-` or `@` are prefixed with a single quote so spreadsheets show them as text instead of running them as formulas.
|
|
226
|
+
|
|
227
|
+
Tickets are subject to the same monthly conversation limit as live chat on your plan (a ticket **is** a conversation) — there's no separate ticket-specific cap or plan gate on the API itself.
|
|
228
|
+
|
|
229
|
+
## Webhooks
|
|
230
|
+
|
|
231
|
+
```typescript
|
|
232
|
+
// Register a webhook
|
|
233
|
+
const webhook = await kerabie.webhooks.create({
|
|
234
|
+
url: 'https://your-server.com/hooks/kerabie',
|
|
235
|
+
events: ['message.sent.customer', 'conversation.created'],
|
|
236
|
+
secret: 'your-signing-secret',
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
// Verify incoming webhooks (Express example)
|
|
240
|
+
import { WebhooksResource } from '@kerabie/sdk';
|
|
241
|
+
import express from 'express';
|
|
242
|
+
|
|
243
|
+
const app = express();
|
|
244
|
+
app.use(express.raw({ type: 'application/json' }));
|
|
245
|
+
|
|
246
|
+
app.post('/hooks/kerabie', (req, res) => {
|
|
247
|
+
const sig = req.headers['x-kerabie-signature'] as string;
|
|
248
|
+
const rawBody = req.body.toString();
|
|
249
|
+
|
|
250
|
+
if (!WebhooksResource.verify(rawBody, sig, process.env.KERABIE_WEBHOOK_SECRET!)) {
|
|
251
|
+
return res.status(401).end();
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const payload = WebhooksResource.parse(rawBody);
|
|
255
|
+
console.log('Event:', payload.event, payload.data);
|
|
256
|
+
res.status(200).end();
|
|
257
|
+
});
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
## Analytics
|
|
261
|
+
|
|
262
|
+
The same numbers as your Kerabie dashboard (cached for up to 5 minutes; tickets and setup for 1 minute).
|
|
263
|
+
|
|
264
|
+
```typescript
|
|
265
|
+
// Conversation, response-time, satisfaction and AI numbers (days: default 30, max 365)
|
|
266
|
+
const summary = await kerabie.analytics.getSummary({ days: 7 });
|
|
267
|
+
summary.totals.conversations; // 186
|
|
268
|
+
summary.deltas.conversations; // 12 → +12% vs the previous 7 days (null if nothing to compare)
|
|
269
|
+
summary.ai.resolvedAlonePct; // 70 → % the AI handled without a human
|
|
270
|
+
summary.ai.agentHoursSaved; // 41 → an estimate
|
|
271
|
+
summary.trend; // [{ date, count, ai, human }, …] oldest first
|
|
272
|
+
|
|
273
|
+
// Team
|
|
274
|
+
const { agents } = await kerabie.analytics.getAgentStats({ period: '7d' });
|
|
275
|
+
|
|
276
|
+
// The deeper numbers behind the dashboard's Analytics page
|
|
277
|
+
const insights = await kerabie.analytics.getInsights({ days: 30 });
|
|
278
|
+
insights.firstResponse.medianSeconds; // 144
|
|
279
|
+
insights.ai.questions[0]; // { question, asked, aiResolvedPct, avgRating, page, trendPct }
|
|
280
|
+
insights.customers.heatmap.cells; // 7 weekdays × 12 two-hour blocks of customer messages
|
|
281
|
+
insights.pages[0]; // { page, views, questions, askRate, topQuestion }
|
|
282
|
+
|
|
283
|
+
// Open tickets by priority and the % still within SLA
|
|
284
|
+
const sla = await kerabie.analytics.getTicketSla();
|
|
285
|
+
sla.withinSlaPct; // 94
|
|
286
|
+
sla.byPriority; // [{ priority: 'URGENT', count: 3, oldestMinutes: 14 }, …]
|
|
287
|
+
|
|
288
|
+
// First-run checklist: connect a channel, train the AI, create an automation
|
|
289
|
+
const setup = await kerabie.analytics.getSetupStatus(); // { channel, ai, automation }
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
`deltas.responseTime` is a percentage where a **negative** number is an improvement. `avgResponseTime` is in seconds.
|
|
293
|
+
|
|
294
|
+
## AI take-over and customer history
|
|
295
|
+
|
|
296
|
+
```typescript
|
|
297
|
+
// List with inbox tab counts, tags and an `aiHandling` flag
|
|
298
|
+
const { data, meta } = await kerabie.conversations.list({ status: 'WAITING' });
|
|
299
|
+
meta.counts; // { all, WAITING, ACTIVE, RESOLVED, CLOSED } — ignores filters
|
|
300
|
+
data.filter((c) => c.aiHandling); // conversations the AI is handling alone
|
|
301
|
+
|
|
302
|
+
// A customer's earlier conversations (newest first, default 5, max 20)
|
|
303
|
+
const { data: earlier } = await kerabie.conversations.getHistory(101, 5);
|
|
304
|
+
|
|
305
|
+
// Stop the AI and hand the conversation to a team member
|
|
306
|
+
await kerabie.conversations.takeOver(101, { agentId: 7 });
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
`takeOver` throws an `HttpError` with status `400` if the AI is no longer handling the conversation (for example someone already took over), so it is safe to call from several places at once.
|
|
310
|
+
|
|
311
|
+
## Error handling
|
|
312
|
+
|
|
313
|
+
```typescript
|
|
314
|
+
import { HttpError } from '@kerabie/sdk';
|
|
315
|
+
|
|
316
|
+
try {
|
|
317
|
+
const conv = await kerabie.conversations.get(9999);
|
|
318
|
+
} catch (err) {
|
|
319
|
+
if (err instanceof HttpError) {
|
|
320
|
+
console.log(err.status); // 404
|
|
321
|
+
console.log(err.message); // "Conversation not found"
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
## TypeScript
|
|
327
|
+
|
|
328
|
+
All types are exported:
|
|
329
|
+
|
|
330
|
+
```typescript
|
|
331
|
+
import type { Conversation, Message, Customer, WebhookPayload } from '@kerabie/sdk';
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
## License
|
|
335
|
+
|
|
336
|
+
MIT
|
|
337
|
+
|
|
338
|
+
## Building with an AI assistant
|
|
339
|
+
|
|
340
|
+
The [`@kerabie/mcp`](https://kerabie.com/docs/sdks/mcp) server gives Claude, Cursor and other MCP clients the API reference, this SDK's types and integration guides, so generated code matches the real API. It needs no API key to help you build, and with one it can work with your account:
|
|
341
|
+
|
|
342
|
+
```json
|
|
343
|
+
{ "mcpServers": { "kerabie": { "command": "npx", "args": ["-y", "@kerabie/mcp"], "env": { "KERABIE_API_KEY": "ker_live_..." } } } }
|
|
344
|
+
```
|