curl -X POST https://tawked.com/v1/chat/menus \
-H "Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: client-8841-bookings-menu" \
-d '{
"title": "الحجوزات",
"url": "https://app.yourplatform.example/tawked/bookings?key=PER_CLIENT_KEY&phone={customer_phone}",
"icon": "site"
}'const res = await fetch("https://tawked.com/v1/chat/menus", {
method: "POST",
headers: {
Authorization: "Bearer tk_live_xxxxxxxxxxxxxxxxxxxx",
"Content-Type": "application/json",
"Idempotency-Key": "client-8841-bookings-menu",
},
body: JSON.stringify({
"title": "الحجوزات",
"url": "https://app.yourplatform.example/tawked/bookings?key=PER_CLIENT_KEY&phone={customer_phone}",
"icon": "site"
}),
});
const data = await res.json();<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://tawked.com/v1/chat/menus",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'title' => '<string>',
'url' => '<string>',
'icon' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}import requests
res = requests.post(
"https://tawked.com/v1/chat/menus",
headers={"Authorization": "Bearer tk_live_xxxxxxxxxxxxxxxxxxxx", "Idempotency-Key": "client-8841-bookings-menu"},
json={
"title": "الحجوزات",
"url": "https://app.yourplatform.example/tawked/bookings?key=PER_CLIENT_KEY&phone={customer_phone}",
"icon": "site"
},
)
data = res.json(){
"id": "5b1d2c3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"title": "الحجوزات",
"url": "https://app.yourplatform.example/tawked/bookings?key=PER_CLIENT_KEY&phone={customer_phone}",
"icon_url": "https://tawked.com/storage/chat-menus/5b1d2c3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e.png",
"frame": "opens",
"created_at": "2026-10-15T09:00:00Z",
"updated_at": "2026-10-15T09:00:00Z"
}{
"error": "invalid_request",
"fields": [
"title"
],
"errors": {
"title": [
"taken"
]
}
}{
"error": "unauthorized"
}{
"error": "live_key_required"
}{
"error": "chat_not_ready",
"reason": "number"
}{
"error": "menu_limit_reached",
"limit": 10
}{
"error": "too_many_requests"
}{
"error": "internal_error"
}{
"error": "chat_unavailable"
}Add a menu
For a connected app’s key alone, with chat:menus in its connection’s grant: an application’s own key, full access included, answers 403 insufficient_scope, and adds its menus in the console. Adds a conversation menu to the application, under the console’s own rules, and answers it. Tawked checks whether the site may be framed inside Tawked Chat and answers it in frame: opens (framed in the conversation), window (the site refuses frames: agents get “Open in a new window”) or unknown (Tawked could not tell). The client’s team sees the tab after reloading Tawked Chat. Put a key made for this one client in the link, never your Tawked key or client secret. A refused field answers 400 invalid_request with fields naming it and errors giving its codes.
curl -X POST https://tawked.com/v1/chat/menus \
-H "Authorization: Bearer tk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: client-8841-bookings-menu" \
-d '{
"title": "الحجوزات",
"url": "https://app.yourplatform.example/tawked/bookings?key=PER_CLIENT_KEY&phone={customer_phone}",
"icon": "site"
}'const res = await fetch("https://tawked.com/v1/chat/menus", {
method: "POST",
headers: {
Authorization: "Bearer tk_live_xxxxxxxxxxxxxxxxxxxx",
"Content-Type": "application/json",
"Idempotency-Key": "client-8841-bookings-menu",
},
body: JSON.stringify({
"title": "الحجوزات",
"url": "https://app.yourplatform.example/tawked/bookings?key=PER_CLIENT_KEY&phone={customer_phone}",
"icon": "site"
}),
});
const data = await res.json();<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://tawked.com/v1/chat/menus",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'title' => '<string>',
'url' => '<string>',
'icon' => '<string>'
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}import requests
res = requests.post(
"https://tawked.com/v1/chat/menus",
headers={"Authorization": "Bearer tk_live_xxxxxxxxxxxxxxxxxxxx", "Idempotency-Key": "client-8841-bookings-menu"},
json={
"title": "الحجوزات",
"url": "https://app.yourplatform.example/tawked/bookings?key=PER_CLIENT_KEY&phone={customer_phone}",
"icon": "site"
},
)
data = res.json(){
"id": "5b1d2c3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e",
"title": "الحجوزات",
"url": "https://app.yourplatform.example/tawked/bookings?key=PER_CLIENT_KEY&phone={customer_phone}",
"icon_url": "https://tawked.com/storage/chat-menus/5b1d2c3e-4f5a-4b6c-8d7e-9f0a1b2c3d4e.png",
"frame": "opens",
"created_at": "2026-10-15T09:00:00Z",
"updated_at": "2026-10-15T09:00:00Z"
}{
"error": "invalid_request",
"fields": [
"title"
],
"errors": {
"title": [
"taken"
]
}
}{
"error": "unauthorized"
}{
"error": "live_key_required"
}{
"error": "chat_not_ready",
"reason": "number"
}{
"error": "menu_limit_reached",
"limit": 10
}{
"error": "too_many_requests"
}{
"error": "internal_error"
}{
"error": "chat_unavailable"
}Authorizations
Authorization: Bearer . An application key (tk_live_, tk_test_) has full access, which covers every product and any product added later, or custom access, chosen when the key is created: one or more of the scopes verify:check, verify:send (includes verify:check), notifications:read, notifications:send (includes notifications:read), email:read, email:send (includes email:read), notifications:templates (includes notifications:read), chat:menus, chat:attendant. No key made on the API keys page holds notifications:templates, full access included: the three template writes answer such a key 403 insufficient_scope. A connected app's key holds it. Nor chat:menus: the five conversation menu operations answer such a key 403 insufficient_scope, and a connected app's key holds it once the client approved it. Nor chat:attendant: the five auto-attendant operations answer such a key 403 insufficient_scope too, and a connected app's key holds it once the client approved it. A call outside the key's access answers 403 insufficient_scope and names the scope it needs in the WWW-Authenticate response header. A partner key (tk_partner_) always has full access. Whatever its access, a key is a server-side secret.
Headers
A 1 to 128 printable-ASCII-character key you generate, scoped to your API key. Replaying it with the same request answers the original 2xx unchanged, with the response header Idempotent-Replayed: true, and runs nothing again; the same key with another request answers 409 idempotency_key_reused, and a request still in flight 409 idempotency_in_progress. A refused request is never stored, so a corrected retry under the same key runs. Keys expire after 24 hours.
Body
The name your client's team sees on the tab: 2 to 30 characters, unique among the menus that show in the application's conversations. Refused: required, length, taken.
The link of the page, saved as sent: https:// only, at most 1024 characters, on a public domain name (no IP address, no port but 443, no user name or password, no token in the host). It may hold the tokens {customer_phone}, {customer_name}, {customer_email} and {conversation_id}, filled when the menu opens, and the key you made for this one client. Refused: required, http, host, length, reserved.
site by default: "site" takes the link's site's own icon (its favicon or apple-touch-icon), as the console does; "none" shows no icon; { "url": "https://..." } is an https link to a PNG, JPEG or WebP of at most 1 MB and at least 32 px on its short side, which Tawked downloads once from a public address and turns into the 64 px icon Tawked Chat shows. Refused: type (not a PNG, JPEG or WebP of at most 1 MB, or an icon of another shape), small (under 32 px), unreachable (the link could not be read).