Website widget

The one part of the API a browser may call directly. Same assistant as WhatsApp, same knowledge base, same booking rules — reached with a key that is safe to publish.

What this surface is

Everything under /api/public authenticates with a widget-scoped API key and is scoped to your organization by that key alone. It sets Access-Control-Allow-Origin: *, because a widget is embedded on domains we cannot know in advance.

  • Create a key with only chat:read, chat:write, bookings:read and bookings:write. Such a key is refused everywhere else, which is what makes publishing it safe.
  • The same routes are also mounted at /api/v1/public. Both work; prefer the shorter form for new code.
  • Widget conversations are keyed by an anonymous session id, never a phone number.

Never publish a key with messages:send

A key carrying that scope reaches the whole private API, whatever else it also holds. Keep the widget key and the server key separate.

Assistant chat

POST/api/public/chat/message
Scope: chat:write

Also mounted at /api/public/message. Rate limited to 20 per minute.

Browser
const res = await fetch('https://api.wpai.co.in/api/public/chat/message', {
  method: 'POST',
  headers: {
    'X-API-Key': 'wpai_pub123.xyz…',
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    message: 'Do you deliver to Bandra?',
    sessionId: localStorage.getItem('wpai_session') ?? undefined,
    name: 'Priya',
  }),
});

const data = await res.json();
localStorage.setItem('wpai_session', data.sessionId); // reuse it for the whole conversation
render(data.reply);
200
{
  "ok": true,
  "sessionId": "web_sess_1756201234_a9f2x",
  "userMessage": "Do you deliver to Bandra?",
  "reply": "Yes — we deliver across the western suburbs. What is your address?",
  "replySource": "ai",
  "leadCaptured": false,
  "bookingCreated": false
}
  • Omit sessionId on the first message and one is generated. Store it and send it back on every subsequent message, or each turn starts a new conversation.
  • leadCaptured and bookingCreated tell you the assistant did something beyond replying — worth showing a confirmation for.
  • This runs the same assistant configured for WhatsApp: same knowledge base, same handoff keywords, same active hours.

Widget chat is not metered

Nothing leaves over WhatsApp, so no message allowance is spent and no wallet is debited. The assistant's own model usage is still recorded against your organization.

History and reset

GET/api/public/chat/history?sessionId=…&limit=50
Scope: chat:read

Oldest first. limit is clamped to 1–100, default 50.

200
{
  "ok": true,
  "sessionId": "web_sess_1756201234_a9f2x",
  "messages": [
    { "id": "68d1…", "fromMe": false, "text": "Do you deliver to Bandra?", "timestamp": "…" },
    { "id": "68d2…", "fromMe": true,  "text": "Yes — we deliver across…",   "timestamp": "…" }
  ]
}

fromMe is from the business's point of view: true is the assistant, false is the visitor.

DELETE/api/public/chat/session?sessionId=…
Scope: chat:write

Deletes the conversation. Use it for a “start over” control.

Bookings

Offering slots

GET/api/public/bookings/slots?date=YYYY-MM-DD
Scope: bookings:read
200
{
  "ok": true,
  "date": "2026-09-01",
  "timezone": "Asia/Kolkata",
  "isOpenDay": true,
  "businessHours": { "start": "09:00", "end": "18:00" },
  "slotDurationMinutes": 30,
  "maxBookingsPerSlot": 3,
  "slots": [ { "time": "09:00", "available": 2, "capacity": 3 } ]
}

An empty slots array means closed, not full

isOpenDay: false with no slots is a day the business does not work. Say "closed" rather than "fully booked" — they are different answers to give a customer.

Taking a booking

POST/api/public/bookings
Scope: bookings:write

Rate limited to 20 per minute.

cURL
curl -X POST https://api.wpai.co.in/api/public/bookings \
  -H "X-API-Key: wpai_pub123.xyz…" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Priya Sharma",
    "phone": "919876543210",
    "date": "2026-09-01",
    "time": "14:30",
    "service": "Consultation",
    "email": "[email protected]"
  }'

time must be a two-digit hour — 09:00, not 9:00. Times are wall-clock in the business's booking timezone, not UTC.

Unlike the staff-facing booking endpoint, this one enforces the schedule:

  • 400 Bookings are not accepted on SUNDAYs
  • 400 Bookings are only accepted between 09:00 and 18:00
  • 409 Selected slot (2026-09-01 14:30) is fully booked

A successful booking upserts a contact tagged website-booking, confirms the slot, and notifies the business.

Embedding the ready-made widget

If you do not want to build the interface, the hosted widget does all of the above. Drop it in with your public key:

Chat widget
<script
  src="https://api.wpai.co.in/public/widget.js"
  data-api-key="wpai_pub123.xyz…"
  data-title="Chat with us"
  data-accent="#25D366"
  defer
></script>

Configure it from the Website Widget screen in the dashboard, which generates the snippet with your key and current settings already filled in.