API reference

These are the endpoints the widget itself calls. You won't normally call these directly — the script tag does it for you — but they're documented here for anyone building a custom integration.

No API key in the browser

Your AI provider key never reaches the browser. It's decrypted server-side per request and used only to call the provider — the widget never sees it, and neither does anything you inspect in devtools.

POST /api/chat

Sends a message and gets a reply. Conversation history is tracked server-side by session_id — the widget generates a random one per page load. Used by the Support Bot widget.

Request
POST /api/chat
Content-Type: application/json

{
  "widget_id": "a1b2c3d4e5",
  "session_id": "sess-a1b2c3d4e5-x7k2p9",
  "message": "Do you ship internationally?"
}
200 Response
{
  "success": true,
  "reply": "Yes — we ship to most countries. Rates are calculated at checkout.",
  "message_count": 4,
  "needs_human": false,
  "sources": [{ "name": "Shipping policy", "url": "https://example.com/shipping" }],
  "collect_contact": false,
  "handoff_message": null
}

GET /api/config/:widgetId

Returns your public widget configuration. Called automatically on page load — no request body, no auth required, and no sensitive fields (like your AI key) are ever included.

200 Response
GET /api/config/:widgetId

{
  "widget_id": "a1b2c3d4e5",
  "widget_type": "support-bot",
  "name": "Acme Co",
  "brand_color": "#111111",
  "icon_url": null,
  "font_family": "Inter",
  "widget_position": "bottom-right",
  "border_radius": 12,
  "button_text": "Chat with us",
  "widget_language": "auto",
  "widget_theme": "classic",
  "welcome_message": null,
  "suggested_questions": [],
  "is_online": true,
  "collect_contact": true,
  "show_sources": true,
  "speech": false,
  "help_url": null,
  "hide_branding": false
}

Public API (Growth and above)

Use your bot from your own software. Create a key under Dashboard → Developers and send it as a Bearer token. Keys are for servers: never put one in a web page. Each key is limited to 120 requests a minute. Responses are JSON, and errors use the shape below. To be told when something happens, connect a webhook under a bot's Integrations (ticket and contact events are sent to your URL, signed).

Example
curl https://techvea.com/api/v1/bots/YOUR_BOT_ID/ask \
  -H "Authorization: Bearer twk_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message":"Do you ship to Canada?","session_id":"customer-42"}'

{ "answer": "Yes, we ship to Canada...", "session_id": "customer-42", "needs_human": false, "sources": [] }
GET/api/v1/botsYour bots.
POST/api/v1/bots/{id}/askAsk the bot a question (message, optional session_id to continue a conversation).
GET/api/v1/bots/{id}/conversationsConversations with every message, newest first. limit (max 100) and before for paging.
GET/api/v1/bots/{id}/ticketsTickets. Optional status=open|pending|solved|closed, limit, before.
GET/api/v1/bots/{id}/tickets/{ticketId}One ticket with its history.
GET/api/v1/bots/{id}/contactsPeople who left their details in the chat.
GET/api/v1/bots/{id}/knowledgeThe sources the bot answers from.
POST/api/v1/bots/{id}/knowledgeAdd a text source: name and text (up to 100,000 characters).

Errors

Every error response follows the same shape.

Error shape
{ "error": "Human-readable message" }
StatusMeaning
400Missing or invalid fields in the request body.
401Missing or expired auth token (dashboard/admin routes only).
403Account suspended, or a permission you don't have.
404widget_id doesn't match an active widget.
422File uploaded but no readable text could be extracted.
429Rate limit exceeded — for example 100 requests/minute per IP on the config route, and a per-visitor burst limit on chat.
500Something failed server-side. Retry, or check back shortly.
503A dependent service (like the AI provider) isn't configured yet.

Switch to the support bot that costs less and does more

Bigger names charge more for the same thing and mark up every AI reply. Techvea runs on your own AI key, so you pay your AI provider's price and nothing on top.