@ownichat/sdk
v0.1.0
Published
Official owni.chat API client: product catalog sync, knowledge sources, allowed domains, webhook verification and the public widget API.
Maintainers
Readme
@ownichat/sdk
Official API client for owni.chat — the AI chat widget that answers from your real catalog.
npm install @ownichat/sdk| | | | --- | --- | | 🛍️ Catalog sync | Push products, prices and stock — batching handled for you | | 📚 Knowledge | Create URL, text and FAQ sources, upload files, trigger reindexing | | 🌐 Domains | Register the origins the widget may load on | | 🔐 Webhooks | Verify HMAC-signed deliveries, with replay protection | | 💬 Widget API | Talk to the public chat API when building your own UI |
Zero runtime dependencies. Node 18+ (uses the global fetch), and works in Deno, Bun,
Cloudflare Workers and other edge runtimes. Ships ESM and CJS with types for both.
Quick start
Create an API key in the owni.chat dashboard under Settings → API keys. It looks like
ak_live_… and must stay server-side.
import { createOwniClient } from '@ownichat/sdk';
const owni = createOwniClient({ apiKey: process.env.OWNI_API_KEY! });
// Everything the key is bound to — including the public key for the widget script tag
const { project } = await owni.me();
console.log(project.project_key); // pk_…That single call is what makes one-field installs possible: the merchant pastes the API key, and your integration discovers the project id and the widget key on its own.
Sync a product catalog
await owni.products.bulkUpsert([
{
external_id: '1042', // your shop's product id
title: 'Merino wool scarf',
description: 'Lightweight, 100% merino.',
url: 'https://shop.example.com/p/1042', // required
price: '49.00',
old_price: '69.00',
currency: 'EUR',
image_url: 'https://shop.example.com/i/1042.jpg',
availability: 'in_stock',
category: 'Accessories',
brand: 'Example',
attributes: [{ name: 'Colour', value: 'Charcoal' }],
},
]);bulkUpsert accepts any number of products and splits them into the API's 100-item batches
for you, sequentially, so a full catalog sync stays within the rate limit.
Other catalog calls:
await owni.products.list({ page: 1, pageSize: 50 });
await owni.products.get('1042');
await owni.products.upsert({ external_id: '1042', title: '…', url: '…' });
await owni.products.delete('1042');Keep external_id equal to your shop's own product id — it is what the widget's
"add to cart" snippet interpolates when a visitor buys from a product card.
Allowed domains
The widget only loads on origins registered for the project. Register the storefront you're installed on so staging sites and secondary domains work without dashboard edits:
await owni.domains.add('staging.shop.example.com'); // idempotent
await owni.domains.list();Knowledge sources
await owni.knowledge.createSource({ type: 'url', name: 'Shipping policy', source_url: 'https://shop.example.com/shipping' });
await owni.knowledge.createSource({ type: 'faq', name: 'FAQ', raw_text: 'Q: …\nA: …' });
const file = new Blob([await readFile('./returns.pdf')]);
await owni.knowledge.uploadFile({ file, filename: 'returns.pdf' });Webhooks
owni.chat signs outgoing webhooks with X-Owni-Signature (HMAC-SHA256 over
<timestamp>.<raw body>) and X-Owni-Timestamp. Verify both — the timestamp is what stops
a captured delivery from being replayed later.
import { parseWebhookRequest } from '@ownichat/sdk/webhooks';
export async function POST(request: Request) {
const body = await request.text(); // raw body, not request.json()
const result = parseWebhookRequest({
body,
headers: request.headers,
secret: process.env.OWNI_WEBHOOK_SECRET!,
});
if (!result.valid) {
return new Response(result.reason, { status: 401 });
}
if (result.event.event === 'lead_captured') {
// …
}
return new Response('ok');
}verifyWebhookSignature is available separately if you already parse the body yourself. Both
work with a Headers object and with a plain Node header record.
The signing secret is shown once when you connect the webhook integration, and can be regenerated from Integrations → Webhooks → Rotate secret. Integrations created before signing existed keep receiving unsigned deliveries until you rotate.
Widget API (browser)
Only needed when building a custom chat UI — the standard embed script already does this.
import { createWidgetClient } from '@ownichat/sdk/widget';
const widget = createWidgetClient({ projectKey: 'pk_…' });
const config = await widget.getConfig();
const session = await widget.createSession({ source_page_url: location.href });Requests are origin-checked server-side, so the page must run on the project domain or one of its registered domains.
Errors and retries
import { OwniApiError, OwniConnectionError } from '@ownichat/sdk';
try {
await owni.products.bulkUpsert(products);
} catch (error) {
if (error instanceof OwniApiError && error.isAuthError) {
// wrong, revoked, expired or under-scoped key — retrying will not help
}
}429 and 5xx responses are retried automatically with exponential backoff, honouring
Retry-After. Configure with maxRetries (default 3) and timeoutMs (default 30 000).
Options
| Option | Default | Notes |
| --- | --- | --- |
| apiKey | — | Required. ak_live_…, server-side only. |
| baseUrl | https://app.owni.chat | Point at your own instance if self-hosting. |
| projectId | resolved via me() | Supply it to skip one round trip. |
| fetch | global fetch | Inject your own for tests or proxies. |
| maxRetries | 3 | Applies to 429 and 5xx only. |
| timeoutMs | 30000 | Per request, including retries individually. |
Related packages
| Package | For |
| --- | --- |
| @ownichat/react | Widget provider, hooks and inline chat panel |
| @ownichat/next | Next.js server component, client hooks and webhook route |
Running a ready-made platform? owni.chat ships official plugins for WordPress/WooCommerce, Shopify and OpenCart — see owni.chat/integrations.
Notes
The widget bundle shipped by owni.chat has its own copy of the browser API types; the two are kept in sync by hand for now. If you spot a drift, the backend's Joi schemas are the source of truth.
License
MIT © owni.chat
