Skip to main content
Version: Peki

Website chat API

The website chat API is what the Peki chat bubble uses on your site. It's public by design: it runs in your visitors' browsers, with no API key. If the ready-made widget doesn't fit your design, you can build your own chat UI on these endpoints. Most sites should use the snippet instead; see Website chat.

Base URL: https://peki.ai/api/widget/{key} Auth: none. The widget key in the path and an x-visitor-id header.

warning

Never put an API key (psk_…) in a web page. The website chat API needs none. If your page needs more than a visitor's own chat, call the Sessions API from your server.

Before you start​

  1. Turn on the website chat for the agent: in Channels → Website chat, switch on Show the chat on your website.

  2. Copy the widget key from the snippet. In

    <script src="https://peki.ai/widget.js" data-key="pw_Xk3n9QeR7tLm2vBc" async></script>

    the key is pw_Xk3n9QeR7tLm2vBc.

  3. Allow your site. Add your site's address, like https://shop.example.com, under Allowed websites with Add website. If the list is empty, any website can use the chat.

  4. Publish the agent. Until then, the chat answers 409 This assistant is not available yet.

The visitor id​

Every request needs an x-visitor-id header: a random id that you create once in the visitor's browser and keep, for example in localStorage. It's the visitor's only credential: whoever has it can read that visitor's conversation, and nothing else.

  • 16 to 64 characters: letters, digits, - and _.
  • Make it random and unguessable, like a UUID without braces.
  • Keep it per browser. A new id means a new, empty chat.
function visitorId() {
let id = localStorage.getItem('peki-visitor');
if (!id) {
id = crypto.randomUUID().replaceAll('-', '');
localStorage.setItem('peki-visitor', id);
}
return id;
}

A missing or malformed id gets 400 Missing or invalid visitor id.

Allowed sites (CORS)​

The browser sends your page's address in the Origin header. If the widget has Allowed websites, only those exact addresses (scheme, host and port, like https://shop.example.com) get through; everything else gets:

{ "statusCode": 403, "message": "This website may not use this chat" }

Add every address your chat runs on, including https://www. variants and staging sites. Up to 20.

Allowed methods are GET, POST and OPTIONS; allowed request headers are content-type and x-visitor-id.

Endpoints​

MethodPathWhat it does
GET/configThe chat's name, color, welcome text and whether it's available
GET/messagesThe visitor's current conversation
POST/messagesSend a message
POST/filesUpload a file
GET/files/{id}Download a file of the visitor's own conversation
POST/sessionsStart a new conversation
GET/feedbackWhether to ask for a rating now
POST/feedbackRate the conversation

A widget that is turned off, or an unknown key, answers every endpoint with 404 This chat is not available.

Get the configuration​

GET /config
curl https://peki.ai/api/widget/pw_Xk3n9QeR7tLm2vBc/config
{
"name": "Bright Smile Dental",
"color": "#1C1A17",
"welcome": { "en": "Hi! How can we help you today?", "tr": "Merhaba! Size nasıl yardımcı olabiliriz?" },
"available": true,
"layout": "bubble"
}
FieldDescription
nameThe agent's name.
colorThe chat's color, as #RRGGBB.
welcomeThe welcome text per language (en, tr). Show the one matching your visitor.
availablefalse until the agent is published.
layoutbubble (a button in the corner) or inline (a chat box in the page).

/config doesn't need a visitor id.

Read the conversation​

GET /messages
x-visitor-id: {visitorId}

Returns the visitor's current conversation, oldest first. After New conversation, or once a conversation has gone quiet long enough to end, the list starts empty.

[
{ "id": "671ca0a1a1b2c3d4e5f60720", "from": "visitor", "text": "Do you have an opening tomorrow?", "at": "2026-10-26T14:05:40.102Z" },
{ "id": "671ca0b3a1b2c3d4e5f60722", "from": "agent", "text": "", "at": "2026-10-26T14:05:40.230Z", "pending": true }
]
FieldDescription
idMessage id.
fromvisitor, agent or team (a person who took over in the Inbox).
textThe text. Empty while pending.
atWhen it was written.
pendingtrue while the agent is still writing. Show a typing indicator.
authorFor team messages: the team member's first name.
attachmentsFiles: { fileId, name, contentType, size }.

Replies arrive by polling. The Peki widget checks every 1.2 seconds while a reply is pending and every 4 seconds otherwise; do the same.

Send a message​

POST /messages
x-visitor-id: {visitorId}
Content-Type: application/json
FieldTypeRequiredRules
textstringOne of the twoUp to 2,000 characters.
attachmentsstring[]One of the twoUp to 5 different file ids from Upload a file.
const KEY = 'pw_Xk3n9QeR7tLm2vBc';
const API = `https://peki.ai/api/widget/${KEY}`;

async function send(text) {
const res = await fetch(`${API}/messages`, {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'x-visitor-id': visitorId() },
body: JSON.stringify({ text }),
});
if (!res.ok) throw new Error((await res.json()).message);
}

Response 202 { "ok": true }. Then poll GET /messages for the reply.

The first message starts a conversation; later ones continue it until it's resolved or has been quiet for the agent's new-session time.

Upload a file​

POST /files
x-visitor-id: {visitorId}
Content-Type: multipart/form-data

One part named file. Photos (JPEG, PNG, WebP, GIF) and documents (PDF, TXT, Markdown, CSV), up to 10 MB. The type is checked from the content. Then send the returned id in a message's attachments within 24 hours.

const form = new FormData();
form.append('file', input.files[0]);
const res = await fetch(`${API}/files`, { method: 'POST', headers: { 'x-visitor-id': visitorId() }, body: form });
const file = await res.json(); // { id, name, contentType, size, kind: "chat" }

Download a file​

GET /files/{id}
x-visitor-id: {visitorId}

Returns a file of the visitor's own conversations: their own uploads and files your team sent them. Anything else, whether it exists or not, is 404 File not found (file_missing). Because it needs the header, load files with fetch and show them from a blob, not with a plain <img src>.

Start a new conversation​

POST /sessions
x-visitor-id: {visitorId}

The visitor's next messages go to a fresh conversation. The old one stays in your Inbox. Response { "ok": true }.

Ratings​

GET /feedback
x-visitor-id: {visitorId}
{ "ask": true, "rating": null }

ask is true when it makes sense to show thumbs up and down: the agent has answered and the conversation was resolved or has been quiet for 2 minutes. rating is the rating already given, if any.

POST /feedback
x-visitor-id: {visitorId}
Content-Type: application/json
FieldTypeRequiredRules
ratingstringYesup or down.
commentstringNoUp to 500 characters.

No other fields are allowed. Response { "ok": true }. The last rating wins. 409 Nothing to rate yet if there's no answered conversation from the last 24 hours.

Limits​

The chat protects itself against floods. Over a limit, the request gets 429 with a message you can show the visitor.

WhatLimitMessage
Messages per visitor20 per minuteYou are sending messages too fast. Please wait a moment.
Messages from one network to one chat200 per hourToo many messages from this network. Please try again later.
Messages to one chat in total120 per minuteThis chat is very busy right now. Please try again in a minute.
New conversations from one network20 per hourToo many new chats from this network. Please try again later.
Files per visitor20 per hourfile_rate
Files from one network60 per hour per chat, 150 per hour in totalfile_rate
File downloads60 per minute per visitor, 600 per minute per networkToo many files opened. Please wait a moment.
Ratings10 per minute per visitor, 100 per hour per network, 120 per minute per chatToo many taps. Please wait a moment.

Files are also limited to 5 per message and 30 per conversation, and the workspace has a daily limit of live replies.

Errors​

StatusMessage or codeWhy
400Missing or invalid visitor idNo x-visitor-id, or not 16–64 letters, digits, -, _.
400Write a message or attach a fileEmpty message.
400file_count, file_type, conversation_fullToo many files, a type that isn't accepted, or 30 files already.
403This website may not use this chatYour site isn't in Allowed websites.
404This chat is not availableUnknown key, or the chat is turned off.
409This assistant is not available yetThe agent isn't published.
409file_takenA file is already attached or can't be found.
413file_sizeThe file is larger than 10 MB.
429see LimitsToo many requests.