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
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.
POST /api/chat
Content-Type: application/json
{
"widget_id": "a1b2c3d4e5",
"session_id": "sess-a1b2c3d4e5-x7k2p9",
"message": "Do you ship internationally?"
}{
"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.
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).
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/bots | Your bots. |
| POST | /api/v1/bots/{id}/ask | Ask the bot a question (message, optional session_id to continue a conversation). |
| GET | /api/v1/bots/{id}/conversations | Conversations with every message, newest first. limit (max 100) and before for paging. |
| GET | /api/v1/bots/{id}/tickets | Tickets. 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}/contacts | People who left their details in the chat. |
| GET | /api/v1/bots/{id}/knowledge | The sources the bot answers from. |
| POST | /api/v1/bots/{id}/knowledge | Add a text source: name and text (up to 100,000 characters). |
Errors
Every error response follows the same shape.
{ "error": "Human-readable message" }| Status | Meaning |
|---|---|
| 400 | Missing or invalid fields in the request body. |
| 401 | Missing or expired auth token (dashboard/admin routes only). |
| 403 | Account suspended, or a permission you don't have. |
| 404 | widget_id doesn't match an active widget. |
| 422 | File uploaded but no readable text could be extracted. |
| 429 | Rate limit exceeded — for example 100 requests/minute per IP on the config route, and a per-visitor burst limit on chat. |
| 500 | Something failed server-side. Retry, or check back shortly. |
| 503 | A dependent service (like the AI provider) isn't configured yet. |