Skip to main content

API Reference

Tork Chat exposes a JSON REST API. All endpoints are proxied through your deployment at https://chat.tork.network/api/proxy/.

Authentication

The widget handles authentication automatically via your tenant slug. For direct API calls, pass your tenant slug in the request body. No API key is required for the public chat endpoints — they are protected by per-tenant rate limiting.

Note: Admin endpoints (knowledge base, widget config, analytics) require a valid Supabase session cookie. These are only accessible from the Admin Portal.
POST/api/proxy/chat

Send a user message and receive a complete AI response. Use this for synchronous integrations where streaming is not required.

Request body (application/json)
{
  "tenant_slug": "your-tenant-slug",
  "session_id": "uuid-v4",          // identifies the conversation
  "message": "What are your hours?",
  "language": "en"                   // optional, auto-detected if omitted
}
Response 200 OK
{
  "reply": "Our hours are Monday–Friday, 9 am to 6 pm.",
  "session_id": "uuid-v4",
  "receipt_id": "rcpt_01J...",       // governance receipt ID
  "pii_detected": false,
  "escalated": false,
  "language": "en",
  "latency_ms": 412
}
POST/api/proxy/chat-stream

Same as /api/proxy/chat but returns a Server-Sent Events stream. Tokens arrive as they are generated — ideal for UI rendering.

Request body (identical to /chat)
{
  "tenant_slug": "your-tenant-slug",
  "session_id": "uuid-v4",
  "message": "Explain your return policy."
}
Response — text/event-stream
data: {"token":"Our"}

data: {"token":" return"}

data: {"token":" policy"}

data: {"token":" is"}

data: {"done":true,"receipt_id":"rcpt_01J...","pii_detected":false}
JavaScript example
const res = await fetch('/api/proxy/chat-stream', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    tenant_slug: 'your-slug',
    session_id: crypto.randomUUID(),
    message: userInput,
  }),
});

const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';

while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  const lines = buffer.split('\n');
  buffer = lines.pop() ?? '';
  for (const line of lines) {
    if (!line.startsWith('data: ')) continue;
    const event = JSON.parse(line.slice(6));
    if (event.token) appendToken(event.token);
    if (event.done) finalise(event);
  }
}
GET/api/proxy/tenant/{slug}

Fetch public configuration for a tenant — used by the widget to load colours, bot name, and greeting on initialisation.

Response 200 OK
{
  "slug": "your-tenant-slug",
  "name": "Acme Corp",
  "bot_name": "Aria",
  "greeting": "Hi! How can I help you today?",
  "accent_color": "#0077CC",
  "theme": "dark",
  "voice_enabled": false,
  "language": "en",
  "plan": "pro"
}

Rate limits

Limits are enforced per tenant per minute (RPM) and reset on a rolling 60-second window. When exceeded, the API returns 429 Too Many Requests with a Retry-After header.

PlanRequests / min
Free10
Starter30
Pro60
Business120
Enterprise300

Error codes

StatusMeaning
400Missing required field (tenant_slug, message).
404Tenant slug not found or inactive.
429Rate limit exceeded. Check Retry-After header.
451Request blocked by Topic Guard.
500Internal error. Retry with exponential backoff.