Developer docs

External API

Authenticate with a project API key and push knowledge or products into your owni.chat AI assistant from your CMS, ERP, or backend.

Overview

The external API is a project-scoped REST interface. Every request must include a valid API key in the Authorization header. Keys are created in the dashboard under API, belong to exactly one project, and can be revoked at any time.

  • Base path: https://app.owni.chat/api/external/v1
  • Auth: Authorization: Bearer ak_live_…
  • Rate limit: 60 requests per minute per client
  • Content type for JSON endpoints: application/json
  • Outbound events: signed webhooks from Integrations → Webhook (see Webhooks)

Authentication

All external endpoints require a project API key. This is not your dashboard login password or JWT session token — it is a dedicated server-to-server secret issued per project.

1. Create a key in the dashboard

  1. Sign in to your owni.chat account and open the target workspace / project.
  2. Open API in the project sidebar.
  3. Enter a label (for example “CMS sync” or “Staging ERP”) and create the key.
  4. Copy the full key immediately. For security it is shown only once. After you leave the page, the dashboard only shows a short prefix such as ak_live_a1b2c3….

2. Key format

Newly issued keys look like:

ak_live_<48 hex characters>
  • Prefix ak_live_ identifies a live project API key.
  • The secret part is 24 random bytes encoded as hex (48 characters).
  • Keys that do not start with ak_ are rejected with 401.

3. Send the Bearer token

Pass the full raw key on every request:

Authorization: Bearer ak_live_your_api_key
curl -X GET \
  https://app.owni.chat/api/external/v1/projects/YOUR_PROJECT_ID/products \
  -H "Authorization: Bearer ak_live_your_api_key"
  • Scheme must be Bearer (capital B) followed by a single space and the key.
  • Do not send the key as a query parameter, cookie, or custom header other than Authorization.
  • Missing or malformed headers return 401 Missing API key. Use Authorization: Bearer <key>.

4. How the server validates the key

owni.chat never stores the raw key. On creation we save only:

  • SHA-256 hash of the full key — used for lookup on each request
  • Key prefix — short public identifier for the UI
  • Scopes, optional expiry, active/revoked flag

On each call the middleware hashes the Bearer value and loads the matching active key. Then it checks expiry, required scope, and that the key’s project_id matches :projectId in the URL. Successful use updates last_used_at.

5. Project binding

Every key is permanently tied to one project UUID. Paths always include that project:

https://app.owni.chat/api/external/v1/projects/{projectId}/…

If you call another project’s ID with this key, the API returns 403 API key does not belong to this project. Create a separate key inside each project you need to automate.

6. Expiry and revocation

  • Keys may be created with an optional expires_at timestamp. After that moment requests fail with 401 API key has expired.
  • Revoking a key in the dashboard sets it inactive immediately. Further requests return 401 Invalid or revoked API key. Revocation cannot be undone — create a new key if needed.
  • Because only the hash is stored, a lost raw key cannot be recovered. Rotate by creating a new key, updating your backend, then revoking the old one.

Typical auth error responses

StatusExample messageCause
401Missing API key. Use Authorization: Bearer <key>No or non-Bearer Authorization header
401Invalid API keyValue does not start with ak_
401Invalid or revoked API keyUnknown hash or revoked key
401API key has expiredexpires_at is in the past
403API key is missing required scope: …Key lacks the scope for this endpoint

Scopes

Each key carries a list of scopes. New keys currently receive both scopes by default. The endpoint still checks the scope required for that route.

ScopeAllows
knowledge.writeCreate knowledge sources, upload training files, trigger re-index
products.writeList, get, upsert, bulk upsert and delete catalog products

Base URL & project ID

Production API root:

https://app.owni.chat/api/external/v1

Replace YOUR_PROJECT_ID with the project UUID from the dashboard (project settings / URL). The same value must match the project that owns the API key.

Add knowledge (text or URL)

Create a knowledge source. Embeddings are rebuilt in the background — usually within about a minute. Requires knowledge.write.

Plain text or FAQ

curl -X POST \
  https://app.owni.chat/api/external/v1/projects/YOUR_PROJECT_ID/knowledge-sources \
  -H "Authorization: Bearer ak_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "text",
    "name": "Shipping policy",
    "raw_text": "We ship worldwide within 2-5 business days..."
  }'

Web page by URL

curl -X POST \
  https://app.owni.chat/api/external/v1/projects/YOUR_PROJECT_ID/knowledge-sources \
  -H "Authorization: Bearer ak_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "url",
    "name": "Help center article",
    "source_url": "https://example.com/help/returns"
  }'

Supported type values include text, faq, url, website, and product_feed (XML feed URL with optional metadata).

Response 201:

{
  "knowledge_source": {
    "id": "6a1f...c2",
    "type": "text",
    "name": "Shipping policy",
    "status": "pending",
    "created_at": "2026-07-09T12:00:00.000Z"
  }
}

Upload files

PDF, TXT, Markdown and CSV files are parsed and indexed automatically. Send multipart/form-data. Max size 10 MB. Requires knowledge.write.

curl -X POST \
  https://app.owni.chat/api/external/v1/projects/YOUR_PROJECT_ID/knowledge-sources/upload \
  -H "Authorization: Bearer ak_live_your_api_key" \
  -F "[email protected]" \
  -F "name=Product manual"

Re-index a source

Re-fetch or rebuild embeddings for an existing source without recreating it. Requires knowledge.write.

curl -X POST \
  https://app.owni.chat/api/external/v1/projects/YOUR_PROJECT_ID/knowledge-sources/SOURCE_ID/reindex \
  -H "Authorization: Bearer ak_live_your_api_key"

Products catalog

Manage shop products used for AI product cards and add-to-cart. All product routes require products.write. Identify items by your shop external_id (SKU / product_id from the feed).

MethodPathPurpose
GET/projects/:id/productsList products (page, page_size)
GET/products/:externalIdGet one product
PUT/products/:externalIdCreate or update one product
POST/products/bulkUpsert up to 100 products
DELETE/products/:externalIdDelete a product

Upsert example

curl -X PUT \
  https://app.owni.chat/api/external/v1/projects/YOUR_PROJECT_ID/products/41494 \
  -H "Authorization: Bearer ak_live_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Demo product",
    "url": "https://shop.example/?product_id=41494",
    "price": "199.00",
    "currency": "UAH",
    "image_url": "https://shop.example/img/41494.jpg"
  }'

Webhooks

Webhooks are the opposite direction of the REST API: owni.chat POSTs chat events to a URL you configure. Use them for Zapier, Make, n8n, HubSpot/Pipedrive via middleware, or your own backend. They are not authenticated with ak_live_… — instead each delivery is signed with a per-project signing secret.

1. Connect an endpoint

  1. In the dashboard open the project → IntegrationsWebhook.
  2. Paste an HTTPS URL that accepts POST with a JSON body (Zapier Catch Hook, Make webhook, or your own route).
  3. Save. Copy the signing secret (whsec_…) immediately — it is shown only on connect or after you rotate it.
  4. Click Send test to deliver a test event and confirm your endpoint returns 2xx.

Store the secret as an environment variable (for example OWNI_WEBHOOK_SECRET). Never put it in client-side code.

2. Payload & headers

Every delivery is a single POST with:

  • Content-Type: application/json
  • X-Owni-Timestamp — Unix seconds when the request was signed
  • X-Owni-Signaturesha256=<hex> HMAC over <timestamp>.<raw body>

Body shape

{
  "event": "lead_captured",
  "data": { … },
  "sent_at": "2026-07-27T18:08:48.425Z"
}

3. Verify the signature

Always verify before trusting the payload:

  1. Read the raw request body as a string. Do not JSON.parse and re-stringify before hashing — byte order must match what was signed.
  2. Compute HMAC-SHA256(secret, timestamp + "." + rawBody) and prefix with sha256=.
  3. Compare to X-Owni-Signature in constant time.
  4. Reject if |now − timestamp| > 300 seconds (replay protection).

Helper packages: @ownichat/sdk/webhooks (parseWebhookRequest) and @ownichat/next/server (createWebhookHandler).

4. Events

EventWhen it firesdata fields
testDashboard → Send testmessage
lead_capturedLead-capture form in a chat flowflow_name, node_label, conversation_id, project_id, fields
conversation.startedNew visitor conversationconversation_id, project_id, visitor_id, mode, source_page_url
conversation.closedVisitor or operator closes the chatconversation_id, project_id, visitor_id, closed_by
handoff.requestedEscalation to a human operatorconversation_id, project_id, visitor_id, reason, mode

handoff.requested reason is one of visitor, anger, flow, ai_failure. Flow builder Webhook nodes may also send a custom event name (default flow_webhook) with flow_id, node_id, and optional contact fields.

Example: lead_captured

{
  "event": "lead_captured",
  "data": {
    "flow_name": "Homepage lead",
    "node_label": "Contact form",
    "conversation_id": "3f2a…",
    "project_id": "9c1b…",
    "fields": [
      { "id": "email", "label": "Email", "type": "email", "value": "[email protected]" },
      { "id": "name", "label": "Name", "type": "text", "value": "Ann" }
    ]
  },
  "sent_at": "2026-07-27T18:08:48.425Z"
}

5. Receive examples

Next.js App Router

// app/api/owni/webhook/route.ts
import { createWebhookHandler } from '@ownichat/next/server';

export const POST = createWebhookHandler({
  onLead: async (fields) => {
    await db.lead.create({ data: fields });
  },
  onConversationStarted: async (data) => {
    await analytics.track('chat_started', data);
  },
  onHandoff: async (data) => {
    await notifyOps(data);
  },
  onConversationClosed: async (data) => {
    await analytics.track('chat_closed', data);
  },
});
// Set OWNI_WEBHOOK_SECRET=whsec_… in the server environment.

Node / any runtime with the SDK

import { parseWebhookRequest, WEBHOOK_EVENTS } from '@ownichat/sdk/webhooks';

export async function POST(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 });
  }

  switch (result.event.event) {
    case WEBHOOK_EVENTS.LEAD_CAPTURED:
      // result.event.data.fields
      break;
    case WEBHOOK_EVENTS.CONVERSATION_STARTED:
    case WEBHOOK_EVENTS.CONVERSATION_CLOSED:
    case WEBHOOK_EVENTS.HANDOFF_REQUESTED:
      break;
  }

  return new Response('ok');
}

6. Delivery rules

  • One attempt per event, 10 second timeout.
  • Respond 2xx quickly; do heavy work asynchronously so the queue does not mark the delivery as failed.
  • Last delivery time and last error are shown under Integrations → Webhook.
  • Rotating the secret invalidates the old one immediately — update your receiver first, then rotate.
  • Native HubSpot / Pipedrive lead sync is separate (Integrations tiles). Webhooks remain the generic path for any other CRM or automation tool.

Errors & limits

StatusMeaning
401Missing, invalid, revoked or expired API key
403Key does not belong to this project or lacks the required scope
400Validation error — check the response message
404Resource not found (source, product, …)
429Rate limit exceeded (60 requests per minute)

Error bodies are JSON objects with a message string. Validation failures may also include an errors array with field details.

Security best practices

  • Call the API only from your backend or trusted automation. Never embed ak_live_… in browser JavaScript, mobile apps, or public repos.
  • Store keys in a secret manager or environment variables, not in source control.
  • Use separate keys per environment (production vs staging) and revoke unused keys.
  • If a key leaks, revoke it in the dashboard immediately and issue a replacement.
  • Prefer short-lived keys (expires_at) for temporary integrations and contractors.
  • Monitor last_used_at in the dashboard to spot unexpected activity.

Ready to automate training?

Create a free account, open your project, and generate an API key in minutes.

Start free