Guest feedback
Guest reviews of locations collected from map services (Yandex Maps, 2GIS, TripAdvisor, Delivery Club) with sentiment analysis, and customer appeals from the company Telegram / WhatsApp bot with replies. Reviews need reviews.view (limited to its locations); appeals need appeals.view.
GET List guest reviews
GET /feedback/reviews
Reviews of the locations where reviews.view applies, newest first, with stars and sentiment (positive, neutral, negative, mixed).
Token scope read
Permission
reviews.view
MCP tool feedback_reviews_list · read-only
Query parameters
page
integer
Page number, starting from 1.
Default 1. Minimum 1.
per_page
integer
Items per page (max 100).
Default 25. Value 1–100.
location_ids
array<integer>
Only these locations.
date_from
date
Created from (inclusive).
date_to
date
Created to (inclusive).
source
string
Review source: YandexMaps, TwoGis, TripAdvisor, DeliveryClub, GoogleMaps.
Maximum length 64.
stars_min
integer
Minimum stars.
Value 1–5.
stars_max
integer
Maximum stars.
Value 1–5.
sentiment
string
Overall sentiment.
positiveneutralnegativemixed
search
string
Search in review text and author name.
Maximum length 200.
Responses
Status Description
200 OK — a page of results with meta
401 unauthorized — Missing, invalid, revoked or expired token
403 forbidden · insufficient_scope · plan_required — Token lacks reviews.view
422 validation_failed — Invalid parameters — see error.fields
429 rate_limited — Too many requests — retry after Retry-After seconds
Request
cURL
Node.js
Python
PHP
MCP
Copy
curl 'https://app.jecheck.io/api/ext/v1/feedback/reviews?date_from=2026-11-01' \
-H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/feedback/reviews?date_from=2026-11-01', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
},
});
const { data, meta } = await res.json();
import os, requests
res = requests.get(
"https://app.jecheck.io/api/ext/v1/feedback/reviews",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
params={"date_from":"2026-11-01"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);
$res = $client->request('GET', 'feedback/reviews', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'query' => [
'date_from' => '2026-11-01',
],
]);
$data = json_decode($res->getBody(), true)['data'];
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "feedback_reviews_list",
"arguments": {
"date_from": "2026-11-01"
}
}
}
{
"data": [
{
"id": 1683,
"location": {
"id": 12,
"name": "Abay Ave"
},
"stars": 2,
"text": "Coffee was great but we waited 25 minutes for a table.",
"author_name": "Olga V.",
"source": "TwoGis",
"sentiment": "mixed",
"sentiment_scores": {
"positive": 0.41,
"neutral": 0.05,
"negative": 0.54
},
"created_at": "2026-11-02T19:01:00+05:00"
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 1,
"has_more": false
}
}
GET Retrieve a guest review
GET /feedback/reviews/{id}
Review with sentence-level sentiment.
Token scope read
Permission
reviews.view
MCP tool feedback_reviews_get · read-only
Path parameters
id
integer
Required
Review id.
Responses
Status Description
200 OK
401 unauthorized — Missing, invalid, revoked or expired token
403 forbidden · insufficient_scope · plan_required — Token lacks reviews.view
404 not_found — The record does not exist or is not visible to the token
422 validation_failed — Invalid parameters — see error.fields
429 rate_limited — Too many requests — retry after Retry-After seconds
Request
cURL
Node.js
Python
PHP
MCP
Copy
curl 'https://app.jecheck.io/api/ext/v1/feedback/reviews/42' \
-H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/feedback/reviews/42', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
},
});
const { data } = await res.json();
import os, requests
res = requests.get(
"https://app.jecheck.io/api/ext/v1/feedback/reviews/42",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);
$res = $client->request('GET', 'feedback/reviews/42', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "feedback_reviews_get",
"arguments": {
"id": 42
}
}
}
{
"data": {
"id": 1683,
"location": {
"id": 12,
"name": "Abay Ave"
},
"stars": 2,
"text": "Coffee was great but we waited 25 minutes for a table.",
"author_name": "Olga V.",
"source": "TwoGis",
"sentiment": "mixed",
"sentiment_scores": {
"positive": 0.41,
"neutral": 0.05,
"negative": 0.54
},
"created_at": "2026-11-02T19:01:00+05:00",
"sentences": [
{
"text": "Coffee was great",
"sentiment": "positive"
},
{
"text": "but we waited 25 minutes for a table.",
"sentiment": "negative"
}
]
}
}
GET Get review statistics by location
GET /feedback/reviews/stats
Number of reviews, average stars and sentiment split per location for the period, plus the total.
Token scope read
Permission
reviews.view
MCP tool feedback_reviews_stats · read-only
Query parameters
location_ids
array<integer>
Only these locations.
date_from
date
Created from (inclusive).
date_to
date
Created to (inclusive).
source
string
Review source: YandexMaps, TwoGis, TripAdvisor, DeliveryClub, GoogleMaps.
Maximum length 64.
Responses
Status Description
200 OK
401 unauthorized — Missing, invalid, revoked or expired token
403 forbidden · insufficient_scope · plan_required — Token lacks reviews.view
422 validation_failed — Invalid parameters — see error.fields
429 rate_limited — Too many requests — retry after Retry-After seconds
Request
cURL
Node.js
Python
PHP
MCP
Copy
curl 'https://app.jecheck.io/api/ext/v1/feedback/reviews/stats?date_from=2026-11-01' \
-H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/feedback/reviews/stats?date_from=2026-11-01', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
},
});
const { data } = await res.json();
import os, requests
res = requests.get(
"https://app.jecheck.io/api/ext/v1/feedback/reviews/stats",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
params={"date_from":"2026-11-01"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);
$res = $client->request('GET', 'feedback/reviews/stats', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'query' => [
'date_from' => '2026-11-01',
],
]);
$data = json_decode($res->getBody(), true)['data'];
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "feedback_reviews_stats",
"arguments": {
"date_from": "2026-11-01"
}
}
}
{
"data": {
"total": {
"reviews": 132,
"avg_stars": 4.3,
"positive": 88,
"neutral": 12,
"negative": 21,
"mixed": 11
},
"locations": [
{
"location": {
"id": 12,
"name": "Abay Ave"
},
"reviews": 70,
"avg_stars": 4.5,
"positive": 51,
"neutral": 6,
"negative": 8,
"mixed": 5
}
]
}
}
GET List customer appeals
GET /feedback/appeals
Conversations started by customers in the company Telegram or WhatsApp bot, newest first, with the last message. Contains customer contacts, so appeals.view is required.
Token scope read
Permission
appeals.view
MCP tool feedback_appeals_list · read-only
Query parameters
page
integer
Page number, starting from 1.
Default 1. Minimum 1.
per_page
integer
Items per page (max 100).
Default 25. Value 1–100.
status
string
Filter by status.
openclosed
Responses
Status Description
200 OK — a page of results with meta
401 unauthorized — Missing, invalid, revoked or expired token
403 forbidden · insufficient_scope · plan_required — Token lacks appeals.view
422 validation_failed — Invalid parameters — see error.fields
429 rate_limited — Too many requests — retry after Retry-After seconds
Request
cURL
Node.js
Python
PHP
MCP
Copy
curl 'https://app.jecheck.io/api/ext/v1/feedback/appeals?status=open' \
-H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/feedback/appeals?status=open', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
},
});
const { data, meta } = await res.json();
import os, requests
res = requests.get(
"https://app.jecheck.io/api/ext/v1/feedback/appeals",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
params={"status":"open"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);
$res = $client->request('GET', 'feedback/appeals', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'query' => [
'status' => 'open',
],
]);
$data = json_decode($res->getBody(), true)['data'];
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "feedback_appeals_list",
"arguments": {
"status": "open"
}
}
}
{
"data": [
{
"id": 340,
"title": "Lost umbrella",
"status": "open",
"contact": {
"id": 77,
"name": "Marat",
"phone": null,
"email": null,
"channel": "telegram"
},
"assignee": null,
"last_message": {
"id": 5521,
"author": {
"type": "customer",
"id": 77,
"uuid": null,
"name": "Marat"
},
"type": "text",
"text": "Hello! I left my umbrella at Abay Ave yesterday.",
"file": null,
"created_at": "2026-11-03T09:12:00+05:00"
},
"created_at": "2026-11-03T09:12:00+05:00"
}
],
"meta": {
"page": 1,
"per_page": 25,
"total": 1,
"has_more": false
}
}
GET Retrieve a customer appeal
GET /feedback/appeals/{id}
The appeal with the whole conversation, oldest message first.
Token scope read
Permission
appeals.view
MCP tool feedback_appeals_get · read-only
Path parameters
id
integer
Required
Appeal id.
Responses
Status Description
200 OK
401 unauthorized — Missing, invalid, revoked or expired token
403 forbidden · insufficient_scope · plan_required — Token lacks appeals.view
404 not_found — The record does not exist or is not visible to the token
422 validation_failed — Invalid parameters — see error.fields
429 rate_limited — Too many requests — retry after Retry-After seconds
Request
cURL
Node.js
Python
PHP
MCP
Copy
curl 'https://app.jecheck.io/api/ext/v1/feedback/appeals/42' \
-H 'Authorization: Bearer $JECHECK_TOKEN'
const res = await fetch('https://app.jecheck.io/api/ext/v1/feedback/appeals/42', {
method: 'GET',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
},
});
const { data } = await res.json();
import os, requests
res = requests.get(
"https://app.jecheck.io/api/ext/v1/feedback/appeals/42",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);
$res = $client->request('GET', 'feedback/appeals/42', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
]);
$data = json_decode($res->getBody(), true)['data'];
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "feedback_appeals_get",
"arguments": {
"id": 42
}
}
}
{
"data": {
"id": 340,
"title": "Lost umbrella",
"status": "open",
"contact": {
"id": 77,
"name": "Marat",
"phone": null,
"email": null,
"channel": "telegram"
},
"assignee": null,
"last_message": {
"id": 5521,
"author": {
"type": "customer",
"id": 77,
"uuid": null,
"name": "Marat"
},
"type": "text",
"text": "Hello! I left my umbrella at Abay Ave yesterday.",
"file": null,
"created_at": "2026-11-03T09:12:00+05:00"
},
"created_at": "2026-11-03T09:12:00+05:00",
"messages": [
{
"id": 5521,
"author": {
"type": "customer",
"id": 77,
"uuid": null,
"name": "Marat"
},
"type": "text",
"text": "Hello! I left my umbrella at Abay Ave yesterday.",
"file": null,
"created_at": "2026-11-03T09:12:00+05:00"
}
]
}
}
POST Reply to a customer appeal
POST /feedback/appeals/{id}/messages
Sends a text message to the customer through the channel of the appeal (Telegram bot or WhatsApp) on behalf of the acting user. Returns the updated conversation.
Token scope write
Permission
appeals.view
MCP tool feedback_appeals_reply
Path parameters
id
integer
Required
Appeal id.
Request body
text
string
Required
Message text.
Length 1–4000.
Responses
Status Description
201 Created
401 unauthorized — Missing, invalid, revoked or expired token
403 forbidden · insufficient_scope · plan_required — Token lacks appeals.view, is read-only
404 not_found — The record does not exist or is not visible to the token
422 validation_failed — Invalid parameters — see error.fields
429 rate_limited — Too many requests — retry after Retry-After seconds
Request
cURL
Node.js
Python
PHP
MCP
Copy
curl -X POST 'https://app.jecheck.io/api/ext/v1/feedback/appeals/42/messages' \
-H 'Authorization: Bearer $JECHECK_TOKEN' \
-H 'Content-Type: application/json' \
-d '{
"text": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}'
const res = await fetch('https://app.jecheck.io/api/ext/v1/feedback/appeals/42/messages', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.JECHECK_TOKEN}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
"text": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}),
});
const { data } = await res.json();
import os, requests
res = requests.post(
"https://app.jecheck.io/api/ext/v1/feedback/appeals/42/messages",
headers={"Authorization": f"Bearer {os.environ['JECHECK_TOKEN']}"},
json={"text":"Seal on fridge #2 is torn, temperature rises to +9 °C."},
)
res.raise_for_status()
data = res.json()["data"]
$client = new GuzzleHttp\Client(['base_uri' => 'https://app.jecheck.io/api/ext/v1/']);
$res = $client->request('POST', 'feedback/appeals/42/messages', [
'headers' => ['Authorization' => 'Bearer ' . getenv('JECHECK_TOKEN')],
'json' => [
'text' => 'Seal on fridge #2 is torn, temperature rises to +9 °C.',
],
]);
$data = json_decode($res->getBody(), true)['data'];
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "feedback_appeals_reply",
"arguments": {
"id": 42,
"text": "Seal on fridge #2 is torn, temperature rises to +9 °C."
}
}
}
{
"data": {
"id": 340,
"title": "Lost umbrella",
"status": "open",
"contact": {
"id": 77,
"name": "Marat",
"phone": null,
"email": null,
"channel": "telegram"
},
"assignee": null,
"last_message": {
"id": 5521,
"author": {
"type": "customer",
"id": 77,
"uuid": null,
"name": "Marat"
},
"type": "text",
"text": "Hello! I left my umbrella at Abay Ave yesterday.",
"file": null,
"created_at": "2026-11-03T09:12:00+05:00"
},
"created_at": "2026-11-03T09:12:00+05:00",
"messages": [
{
"id": 5521,
"author": {
"type": "customer",
"id": 77,
"uuid": null,
"name": "Marat"
},
"type": "text",
"text": "Hello! I left my umbrella at Abay Ave yesterday.",
"file": null,
"created_at": "2026-11-03T09:12:00+05:00"
}
]
}
}