jeCheckDevelopers
Get an API key
Developers/API reference/Guest feedback

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.

GETList guest reviews

GET /feedback/reviews

Reviews of the locations where reviews.view applies, newest first, with stars and sentiment (positive, neutral, negative, mixed).

Token scoperead
Permission reviews.view
MCP toolfeedback_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

StatusDescription
200OK — a page of results with meta
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks reviews.view
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
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"
        }
    }
}
Response 200
{
  "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
  }
}

GETRetrieve a guest review

GET /feedback/reviews/{id}

Review with sentence-level sentiment.

Token scoperead
Permission reviews.view
MCP toolfeedback_reviews_get · read-only

Path parameters

id integer Required
Review id.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks reviews.view
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
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
        }
    }
}
Response 200
{
  "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"
      }
    ]
  }
}

GETGet 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 scoperead
Permission reviews.view
MCP toolfeedback_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

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks reviews.view
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
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"
        }
    }
}
Response 200
{
  "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
      }
    ]
  }
}

GETList 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 scoperead
Permission appeals.view
MCP toolfeedback_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

StatusDescription
200OK — a page of results with meta
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks appeals.view
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
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"
        }
    }
}
Response 200
{
  "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
  }
}

GETRetrieve a customer appeal

GET /feedback/appeals/{id}

The appeal with the whole conversation, oldest message first.

Token scoperead
Permission appeals.view
MCP toolfeedback_appeals_get · read-only

Path parameters

id integer Required
Appeal id.

Responses

StatusDescription
200OK
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks appeals.view
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
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
        }
    }
}
Response 200
{
  "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"
      }
    ]
  }
}

POSTReply 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 scopewrite
Permission appeals.view
MCP toolfeedback_appeals_reply

Path parameters

id integer Required
Appeal id.

Request body

text string Required
Message text. Length 1–4000.

Responses

StatusDescription
201Created
401unauthorized — Missing, invalid, revoked or expired token
403forbidden · insufficient_scope · plan_required — Token lacks appeals.view, is read-only
404not_found — The record does not exist or is not visible to the token
422validation_failed — Invalid parameters — see error.fields
429rate_limited — Too many requests — retry after Retry-After seconds
Request
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."
        }
    }
}
Response 201
{
  "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"
      }
    ]
  }
}