Smart Aleck · Documentation

Developer guide

Widget embedding, configuration, the questionnaire schema and the partner REST API — everything you need for integration.

Overview

Base URL: https://api.chkuiskolopi.ge/api

There are two kinds of access:

Most bots are managed without code, from the portal — you need the API only if you want your own automation.

Quick start

  1. Create a bot in the portal and take its public_key (visible in the embed-code modal).
  2. Paste one line at the end of your site's <body>:
<script src="https://api.chkuiskolopi.ge/api/scrap/widget.js?key=PUBLIC_KEY"></script>
  1. Optionally test the bot directly over the API:
curl -X POST -H "Content-Type: application/json" \
  -d '{"partner_key":"PUBLIC_KEY","query":"გამარჯობა","session_id":"test-1","deviceInfo":{"device_name":"curl","ip_address":""}}' \
  https://api.chkuiskolopi.ge/api/scrap/widget/query/

Embedding the widget

The script creates the floating button (bottom-right corner) and the chat panel by itself. No server-side setup, CSS or extra code is needed.

Session

CORS

The widget endpoints (/api/scrap/widget/…) accept any Origin — the key travels in the body and no cookies are used. Partners do not need to configure CORS.

The domain list (allowed domains) — the security gate

The portal's test window: a domain-locked bot returns 403 domain_not_allowed in the portal preview (the preview runs on a different domain) — that is the protection working, not a bug. Remove the domain temporarily for testing.

Widget configuration (config)

A bot's config is a JSON object. It is edited from the portal, by asking Ostati, or via PATCH. Changes are merged into the existing object — send only the key you want to change, the rest stays untouched. Before every change the previous state is saved automatically (restorable).

Greeting — greeting

The Enter key — enter_sends

Behaviours — behaviors

An object of boolean switches; whatever is not listed is off:

KeyWhat it enables
typingA "typing…" animation while waiting for the answer
processingOn long (>100 chars) questions an interim bubble "processing the information ⏳"
resetA new session via the text "new chat"/"start over"/"/reset" or the ↺ button (fully client-side)
debounceMessages sent within ~2.5 s are merged into one question
voiceThe 🎤 button — speech to text (Georgian); the text lands in the field and the visitor sends it themselves
file_uploadThe 📎 button — uploading a document (PDF/DOCX/XLSX/CSV/TXT) or an image (JPG/PNG/WEBP); the content (OCR included) accompanies the next question

Voice is paid: transcription is charged to the partner's balance at the moment of transcription. On a disabled behaviour the corresponding endpoint returns 403; balance < $1 → 402. While recording, 10 s of silence → auto-stop and transcription; pure silence is not transcribed and costs nothing; one recording is at most 15 minutes.

Look — theme

Colours and shape are passed to the widget as CSS variables. Without a theme — the default blue/white look. All fields are optional; an invalid value is simply ignored.

GroupKeys
Colours (hex or rgb/rgba)accent (main — button/header/Send/user bubble), on_accent, panel, bot_bg/bot_text, user_bg/user_text, field_bg/field_text/field_border/line, muted
Shape (px or %)radius (panel), bubble_radius, field_radius, btn_radius, launcher_radius (50%=circle, 28px=pill, 12px=square)
Floating buttonlauncher_icon (an emoji) or launcher_logo (an image URL, via logo upload). If a logo is set — the icon is ignored
Brandinghide_branding: true → the "powered by chkuiskolopi.ge" link is hidden (white-label)

Markdown rendering in answers

The widget automatically renders the bot's answers as Markdown — for every bot, no config needed: bold/italic, headings (#######), bullet and numbered lists, quotes (>), inline code and code blocks, links [text](https://…) (new tab; http/https/mailto), paragraphs.

Security: the text is HTML-escaped first and formatted after — <script> and other HTML never execute.

Tip: write in the bot's instruction: "Format answers with Markdown: key terms in bold, options as a bullet list, sections with headings, long answers in paragraphs."

The questionnaire — the button-tree schema

Stored in config.questionnaire. The tree runs entirely in the widget (in the browser) — only one ordinary text question reaches the server, at the end of the questionnaire. Without a questionnaire the widget is a free-form chat.

Step schema

"step_key": {
  "text": "the bot's message at this step",
  "buttons": [
    { "label": "button caption", "value": "stored value", "next_step": "next_step_key" }
  ],
  "show_stop_button": true,
  "allow_custom_input": false
}
FieldMeaning
textShown in the bot's bubble when the step opens
buttons[].labelWritten on the button and added as the user's bubble when chosen
buttons[].valueStored among the answers
buttons[].next_stepThe key of the next step; "END" or empty → finish
show_stop_buttontrue → a grey "Stop" button is added
allow_custom_inputtrue → the text field is enabled; typed text becomes the answer and control moves to the first button's next_step (if there is no button — END)

Reserved "_" keys

Keys starting with an underscore inside questionnaire are fields, not steps:

KeyMeaningDefault
_final_promptThe closing instruction sent to the bot together with the answers at the end of the questionnaire"Help the visitor based on these answers."
_stop_promptSame, for a questionnaire abandoned via "Stop""The visitor stopped the questionnaire. Help based on the answers so far."

Never start a step name with "_" — the engine will not treat it as a step.

Engine behaviour

Full example (an imaginary café bot)

{
  "questionnaire": {
    "start": {
      "text": "გამარჯობა! რით დაგეხმაროთ?",
      "buttons": [
        { "label": "მენიუ მაინტერესებს", "value": "menu", "next_step": "menu_type" },
        { "label": "მაგიდის დაჯავშნა", "value": "booking", "next_step": "guests" }
      ],
      "show_stop_button": true
    },
    "menu_type": {
      "text": "რა გირჩევნიათ?",
      "buttons": [
        { "label": "ყავა და დესერტი", "value": "coffee", "next_step": "END" },
        { "label": "საუზმე", "value": "breakfast", "next_step": "END" }
      ],
      "show_stop_button": true
    },
    "guests": {
      "text": "რამდენი სტუმარი იქნებით? (შეგიძლიათ აკრიფოთ)",
      "buttons": [
        { "label": "2", "value": "2", "next_step": "END" },
        { "label": "4", "value": "4", "next_step": "END" }
      ],
      "allow_custom_input": true,
      "show_stop_button": true
    }
  }
}

The partner REST API

Authorization

POST /user/registration/

Body: {email, password, deviceInfo:{device_name, ip_address}, source:"partner"} → email verification starts. An account registered with "source":"partner" receives the $5 gift on verification and is returned to the portal.

POST /user/login/

Body: {email, password}{access_token, refresh_token}. Every partner endpoint requires the Authorization: Bearer <access_token> header; on expiry → 401, log in again.

GET /user/api-key/ · legacy

{api_key} — the old shared key. POST /user/api-key/regenerate/ creates a new one and invalidates the old — this only breaks widgets embedded with the legacy key; per-agent public_keys are unaffected. For new integrations always use the bot's public_key.

Bots (agents)

The agent object: {id, name, public_key, system_prompt, enabled_tools, model_tier, is_active, allowed_domains, config, telegram_connected, telegram_bot_username, facebook_connected, facebook_page_name, …}

GET /scrap/partner/available-tools/

{"tools":[{id, label, description}]} — the catalogue of enableable tools: web search, memory, the virtual screen/browser, power-level switching and hand-offs to the experts. enabled_tools: [] = the full base set without hand-offs; a non-empty list = only the listed ones. Unknown IDs are silently ignored.

GET /scrap/partner/agents/

{"agents":[…]} — only your own bots.

POST /scrap/partner/agents/create/

Body: {name, system_prompt, model_tier, enabled_tools, allowed_domains?, config?}201 + agent.

curl -X POST -H "Authorization: Bearer $T" -H "Content-Type: application/json" \
  -d '{"name":"Consultant","system_prompt":"You are...","enabled_tools":[],"model_tier":"cheap"}' \
  https://api.chkuiskolopi.ge/api/scrap/partner/agents/create/
PATCH /scrap/partner/agents/<id>/

Partial update — only the sent fields change. config is merged into the existing object (send only the key you are changing); before every change the previous state is saved automatically. is_active: false → the widget stops. → 200 + agent · 404 if the bot is not found.

POST /scrap/partner/agents/<id>/transfer/

Body: {recipient: "email"} → the bot moves to the recipient's account. Irreversible. Errors: recipient_required, agent_not_found, recipient_not_found, recipient_not_unique, cannot_transfer_to_self.

Knowledge files

GET /scrap/partner/agents/<id>/files/

{files:[{id, file_name, char_count, created_at}], total_chars, max_total_chars: 30000, max_files: 5}

POST /scrap/partner/agents/<id>/files/upload/

multipart, field file. Only the text is kept — the original does not stay. Types: PDF, DOCX, XLSX, TXT, CSV, MD, JSON, LOG. A scanned PDF yields no text → extraction_failed.

curl -X POST -H "Authorization: Bearer $T" -F "file=@catalog.pdf" \
  https://api.chkuiskolopi.ge/api/scrap/partner/agents/7/files/upload/
POST /scrap/partner/agents/<id>/files/<file_id>/delete/

{ok: true, total_chars}. Irreversible (the original is not stored).

Error codes: file_required · too_many_files (max 5) · file_too_large (max 5MB) · unsupported_type · extraction_failed · knowledge_limit_exceeded (30,000 characters total; the response includes used, file_chars, limit).

POST /scrap/partner/agents/<id>/logo/

multipart, field file — PNG/JPG/WEBP/GIF, ≤ 1MB → {"url": "https://…"}. Put the returned URL into config.theme.launcher_logo. Errors: file_required, unsupported_type, file_too_large, upload_failed.

Channels — Telegram and Messenger

POST /scrap/partner/agents/<id>/telegram/connect/

Body: {bot_token: "the @BotFather token"}. The server validates the token and wires the bot automatically — no other step needed. → {ok: true, telegram_connected: true, telegram_bot_username}. The token is never returned in responses. Errors: bot_token_required, invalid_bot_token, setwebhook_failed, telegram_unreachable, agent_not_found.

POST /scrap/partner/agents/<id>/telegram/disconnect/

{ok: true, telegram_connected: false}.

POST /scrap/partner/agents/<id>/facebook/connect/

Body: {page_token: "Page Access Token"} (from your verified Meta business app). → {ok: true, facebook_connected: true, facebook_page_name, webhook_url, verify_token} — put those two values into your Meta app's Webhooks section (Callback URL + Verify Token) and subscribe the page's messages, messaging_postbacks events. The page token is never returned in responses. Errors: page_token_required, invalid_page_token, facebook_unreachable, agent_not_found.

POST /scrap/partner/agents/<id>/facebook/disconnect/

{ok: true, facebook_connected: false}.

Balance and top-up

POST /scrap/get-user-balance/

Body: {} (Bearer) → {balance} (USD).

POST /scrap/recharge/smart-router/

Body: {amount: ≥10} (USD) → a payment link: from Georgia — by card in GEL (at the National Bank rate), from abroad — in USD (PayPal). Look for payment_url or approval_url in the response and redirect the user. 400 — the amount is below 10 USD.

Widget endpoints (no authorization)

GET /scrap/widget.js?key=<public_key>

→ the widget JavaScript — already carrying that bot's questionnaire, greeting, behaviours and theme.

POST /scrap/widget/query/

Body: {partner_key, query, session_id, deviceInfo:{device_name, ip_address}}

200: {response, DocumentLinks, chat_id, …} — the main field is response (the bot's answer, Markdown text); the rest are auxiliary fields and may be empty.

Errors: 400 partner_key is required · 401 Invalid partner_key · 402 balance < $1 · 403 domain_not_allowed.

/scrap/widget/transcribe/ and /scrap/widget/upload/ are the widget's internal endpoints (the 🎤 and 📎 buttons use them automatically): they work only when the corresponding behaviour is enabled (403 when off), voice is paid, files ≤ 6MB — documents and images (images and scanned PDFs are read by OCR).

Error reference

CodeWhenWhat to do
400Bad request — a field is missing or has the wrong type (config_must_be_object, Min amount 10 USD…)Fix the body per the error in the response
401Invalid/expired token, or Invalid partner_key (including the key of a deactivated bot)Log in again, or check the key and the bot's active flag
402Balance exhausted (the widget works on balance ≥ $1)Top up
403domain_not_allowed — the site is not on the domain list; or the endpoint of a disabled behaviourAdd the domain / enable the behaviour
404Bot/file not found (or belongs to someone else)Check the id
413The file is too largeShrink the file
502An external channel (Telegram/Facebook) is temporarily unreachableRetry in a little while

Limits reference

WhatLimit
Knowledge files per bot5 files · each ≤ 5MB · total text ≤ 30,000 characters
Widget session1 hour of inactivity (refreshed on activity)
Widget 🎤 voiceone recording ≤ 15 min; auto-stop after 10 s of silence; pure silence is free
Telegram voice≤ 3 minutes — on longer ones the bot asks for a shorter message (cost 0)
Messenger audio≤ ~2MB
Widget 📎 files≤ 6MB (PDF/DOCX/XLSX/CSV/TXT/JPG/PNG/WEBP)
Logo≤ 1MB (PNG/JPG/WEBP/GIF)
Balancethe widget works at ≥ $1; minimum top-up $10; $5 gift on registration

Best practices


Still have a question? The portal's Ostati answers technical questions too, or write to us: info@chkuiskolopi.ge