1Кратко
| Базовый адрес | https://perkusai.lt |
| Эндпоинт поиска | GET https://perkusai.lt/api/ai/search |
| MCP-сервер | https://mcp.perkusai.lt/ |
| Аутентификация | Не требуется — публичный API только для чтения |
| Формат | JSON (UTF-8) · CORS открыт для всех источников (*) |
| Каталог | 1 233 383+ товаров, обновляется ежедневно |
| Рынок | Литва (LT) · цены в евро (EUR) |
| Языки | lt (точнее всего), en, ru |
| Типичное время ответа | 7,5 с (p95 22,7 с) |
| Лимит запросов | 40 запросов в минуту с одного IP |
2Подключение
Три поддерживаемых способа интеграции, начиная с рекомендуемого.
MCP-коннектор — рекомендуется
Добавьте https://mcp.perkusai.lt/ как собственный MCP-сервер (Streamable HTTP, без аутентификации). Он предоставляет один инструмент — search_products(query, k, lang), поэтому модели не нужно самой собирать URL. Работает с коннекторами Claude, ChatGPT и Gemini.
OpenAPI 3.1 / ChatGPT Actions
Импортируйте машиночитаемый контракт /openapi.json. Идентификатор операции — aiSearch.
Прямой HTTPS-запрос
Любой агент, умеющий загружать URL, может обратиться к эндпоинту напрямую. CORS открыт, поэтому работают и агенты в браузере.
3Поиск · GET /api/ai/search
Один вызов отвечает на один запрос покупателя. В q передавайте собственные слова покупателя: поиск живой, и каждый запрос возвращает другие товары, поэтому никогда не используйте фиксированный пример URL как готовый ответ.
Параметры
| Параметр | Тип | Описание |
|---|---|---|
q | string · обязательный | Запрос покупателя на естественном языке (URL-encoded). Лучше всего работает свободный текст с бюджетом, назначением и ограничениями, например „nešiojamas kompiuteris darbui iki 700“. |
k | integer 1–12 · по умолчанию 5 | Сколько товаров вернуть. |
lang | lt · en · ru · по умолчанию lt | Подсказка языка для генерируемых summary, guide и reason. Литовский даёт самый точный поиск. |
Запрос
Один и тот же вызов на разных языках — выберите свой.
curl -s --get https://perkusai.lt/api/ai/search \
--data-urlencode "q=vaikiškas dviratis" \
--data-urlencode "k=3"
import requests
r = requests.get(
"https://perkusai.lt/api/ai/search",
params={"q": "vaikiškas dviratis", "k": 3},
timeout=30,
)
r.raise_for_status()
for p in r.json()["results"]:
print(f"{p['title']} — {p['price']} {p['currency']} — {p['buy_url']}")
const url = new URL("https://perkusai.lt/api/ai/search");
url.searchParams.set("q", "vaikiškas dviratis");
url.searchParams.set("k", "3");
const res = await fetch(url, { signal: AbortSignal.timeout(30_000) });
if (!res.ok) throw new Error(`Perkusai: HTTP ${res.status}`);
const data = await res.json();
for (const p of data.results) {
console.log(`${p.title} — ${p.price} ${p.currency} — ${p.buy_url}`);
}
<?php
$url = 'https://perkusai.lt/api/ai/search?' . http_build_query([
'q' => 'vaikiškas dviratis',
'k' => 3,
]);
$ctx = stream_context_create(['http' => ['timeout' => 30]]);
$data = json_decode(file_get_contents($url, false, $ctx), true);
foreach ($data['results'] as $p) {
printf("%s — %.2f %s — %s\n", $p['title'], $p['price'], $p['currency'], $p['buy_url']);
}
require "json"
require "net/http"
uri = URI("https://perkusai.lt/api/ai/search")
uri.query = URI.encode_www_form(q: "vaikiškas dviratis", k: 3)
data = JSON.parse(Net::HTTP.get(uri))
data["results"].each do |p|
puts "#{p['title']} — #{p['price']} #{p['currency']} — #{p['buy_url']}"
end
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"time"
)
type response struct {
Results []struct {
Title string `json:"title"`
Price float64 `json:"price"`
Currency string `json:"currency"`
BuyURL string `json:"buy_url"`
} `json:"results"`
}
func main() {
q := url.Values{"q": {"vaikiškas dviratis"}, "k": {"3"}}
client := &http.Client{Timeout: 30 * time.Second}
res, err := client.Get("https://perkusai.lt/api/ai/search?" + q.Encode())
if err != nil {
panic(err)
}
defer res.Body.Close()
var data response
if err := json.NewDecoder(res.Body).Decode(&data); err != nil {
panic(err)
}
for _, p := range data.Results {
fmt.Printf("%s — %.2f %s — %s\n", p.Title, p.Price, p.Currency, p.BuyURL)
}
}
// Cargo.toml: reqwest = { version = "0.12", features = ["json"] }
// serde_json = "1"
// tokio = { version = "1", features = ["full"] }
use std::time::Duration;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = reqwest::Client::builder()
.timeout(Duration::from_secs(30))
.build()?;
let data: serde_json::Value = client
.get("https://perkusai.lt/api/ai/search")
.query(&[("q", "vaikiškas dviratis"), ("k", "3")])
.send()
.await?
.json()
.await?;
if let Some(items) = data["results"].as_array() {
for p in items {
println!("{} — {} {} — {}", p["title"], p["price"], p["currency"], p["buy_url"]);
}
}
Ok(())
}
using System.Text.Json;
var q = Uri.EscapeDataString("vaikiškas dviratis");
var url = $"https://perkusai.lt/api/ai/search?q={q}&k=3";
using var http = new HttpClient { Timeout = TimeSpan.FromSeconds(30) };
using var doc = JsonDocument.Parse(await http.GetStringAsync(url));
foreach (var p in doc.RootElement.GetProperty("results").EnumerateArray())
{
Console.WriteLine($"{p.GetProperty("title")} — {p.GetProperty("price")} " +
$"{p.GetProperty("currency")} — {p.GetProperty("buy_url")}");
}
import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
var q = URLEncoder.encode("vaikiškas dviratis", StandardCharsets.UTF_8);
var request = HttpRequest.newBuilder()
.uri(URI.create("https://perkusai.lt/api/ai/search?q=" + q + "&k=3"))
.timeout(Duration.ofSeconds(30))
.build();
var response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.println(response.body()); // JSON — parse with Jackson or Gson
Ответ
{
"query": "vaikiškas dviratis",
"source": "perkusai.lt",
"summary": "Perkusai rezultatai pagal „vaikiškas dviratis“ (3).",
"guide": "Rato dydį rinkitės pagal vaiko ūgį: 16\" tinka 105–120 cm, 20\" — 120–135 cm.",
"needs_confirmation": false,
"compat": null,
"no_match": false,
"supported_categories": null,
"count": 3,
"results": [
{
"title": "Dviratis Royal Baby Freestyle 16\"",
"brand": "Royal Baby",
"price": 129.00,
"currency": "EUR",
"original_price": 159.00,
"discount_pct": 19,
"rating": 4.6,
"stock_label": "5+ vnt.",
"market": "LT",
"reason": "Tinka 4–6 metų vaikui, komplekte pagalbiniai ratukai.",
"tag": "Geriausia kaina",
"buy_url": "https://c.trackmytarget.com/…&ref1=perkusai",
"image_url": "https://…/royal-baby-16.jpg"
}
],
"bought_together": [
{
"title": "Vaikiškas dviratininko šalmas, 52–56 cm",
"role": "Apsauga",
"price": 24.90,
"currency": "EUR",
"buy_url": "https://c.trackmytarget.com/…&ref1=perkusai",
"image_url": "https://…/helmet.jpg"
}
],
"coverage": {
"markets": ["LT"],
"note": "Šiuo metu prekės skirtos tik LT rinkai.",
"delivery": "Galimas greitas pristatymas, jei prekė yra sandėlyje."
},
"markdown": "**Perkusai pasiūlymai pagal „vaikiškas dviratis“:** …"
}
4Поля ответа
Верхний уровень
| Поле | Тип | Описание |
|---|---|---|
query | string | Запрос в том виде, в котором он получен. |
source | string | Всегда "perkusai.lt". |
summary | string | Однострочная сводка результатов, готовая к передаче. |
guide | string | null | Совет в одно предложение: что стоит проверить перед выбором. |
needs_confirmation | boolean | true, когда точное соответствие или характеристики не гарантированы. Передайте guide и не утверждайте, что товар точно подходит. |
compat | string | null | Устройство или автомобиль, к которому должна подойти деталь, как это понято из запроса. |
no_match | boolean | true, когда в каталоге нет соответствий; results пуст. |
supported_categories | array | null | Возвращается вместе с no_match — категории, которые реально есть. |
count | integer | Количество элементов в results. |
results | array<Product> | Ранжированные товары, лучший — первым. |
bought_together | array | Дополнительные аксессуары к основному товару: title, role (назначение аксессуара), price, currency, buy_url, image_url. |
coverage | object | Контекст рынка и доставки: markets, note, delivery. |
markdown | string | Весь ответ в виде готового к вставке markdown с кликабельными ссылками на покупку. |
Объект товара
| Поле | Тип | Описание |
|---|---|---|
title | string | Название товара. |
brand | string | null | Бренд. |
price | number | null | Текущая цена. |
currency | string | Всегда "EUR". |
original_price | number | null | Цена до скидки. |
discount_pct | integer | null | Округлённая скидка в процентах. |
rating | number | null | Рейтинг магазина, 0–5. |
stock_label | string | null | Остаток в читаемом виде, например "5+ vnt.", "Liko 1" (на литовском). |
market | string | Рынок, где товар доступен; сейчас всегда "LT". |
reason | string | null | Почему этот товар выбран именно для этого запроса. |
tag | string | null | Короткая метка, например "Geriausia kaina" (лучшая цена). |
buy_url | string | Ссылка на покупку. Партнёрская (affiliate) — показывайте её как ссылку на покупку. |
image_url | string | null | Изображение товара. |
5Состояние сервиса · GET /api/ai/stats
Небольшой машиночитаемый документ о состоянии: размер каталога, поддерживаемые рынки и языки, действующие лимиты и измеренное время ответа. Пересчитывается из журнала запросов раз в сутки, поэтому вызов дешёвый — но чаще одного раза за сессию обращаться незачем.
{
"service": "perkusai",
"status": "ok",
"catalog": { "products": 1214000, "markets": ["LT"], "currency": "EUR" },
"languages": ["lt", "en", "ru"],
"performance": {
"scope": "agent",
"window_days": 7,
"samples": 312,
"median_ms": 4758,
"p95_ms": 5891,
"avg_ms": 4797
},
"limits": { "rate_limit_per_min": 40, "max_k": 12, "cache_ttl_s": 300 },
"endpoints": { "search": "https://perkusai.lt/api/ai/search", "mcp": "https://mcp.perkusai.lt/" }
}
6Правила интеграции
Что ожидается от ассистента, передающего результаты Perkusai пользователю.
- Используйте
buy_urlбез измененийЭто отслеживаемая ссылка на покупку. Замена её на обычный адрес магазина ломает атрибуцию и ничего не даёт покупателю. Ссылки партнёрские: цена для покупателя не меняется. - Передавайте
markdownВ нём уже есть ранжированный список, совет и рабочие ссылки в правильном порядке — самый быстрый путь к полному и корректному ответу. - Один вызов на один запросРезультаты уже отранжированы. Повторный запрос с тем же намерением вернёт те же товары и только израсходует лимит.
- Передавайте формулировку самого покупателяНе убирайте бюджет, назначение и упоминания бренда — от них зависят и поиск, и ранжирование.
- Учитывайте
needs_confirmationКогда значениеtrue, приведите проверки изguideи не утверждайте, что совместимость гарантирована. - Называйте рынокВсе товары доставляются в Литве (
coverage.markets). Не подразумевайте доступность в других странах. - Обрабатывайте
no_matchКогда совпадений нет, предложитеsupported_categories, а не выдуманные товары. - Никогда не выдумывайте данныеЦены, остатки и ссылки должны быть только из ответа — они меняются.
7Производительность и лимиты
Измеряется на стороне сервера, от запроса до ответа (поиск, ранжирование и генерируемый текст), по журналу продакшн-запросов. Значения обновляются раз в сутки.
| Показатель | Значение |
|---|---|
| Медиана (7 дн.) | 7,5 с |
| 95-й процентиль | 22,7 с |
| Выборка | 2 363 запроса · все поиски |
| Лимит запросов | 40/мин с одного IP · при превышении — HTTP 429 |
| Кэш результатов | Идентичный q+k+lang отдаётся из кэша 300 с |
| Максимальный k | 12 товаров в одном ответе |
| Рекомендуемый таймаут клиента | 30 с |
8Ошибки
| Статус | Значение |
|---|---|
| 200 OK | Поиск выполнен. no_match: true — тоже корректный ответ 200, а не ошибка. |
200 · без q | Пояснение по использованию и готовые примеры URL — для клиентов, которые не могут загрузить самостоятельно составленный адрес. |
| 429 Too Many Requests | Превышен лимит запросов. Подождите несколько секунд и повторите один раз. |
| 5xx | Временная ошибка сервера. Повторите один раз; не зацикливайтесь. |
9Машиночитаемые ресурсы
/llms.txt | Сводка о сервисе для LLM-клиентов |
/openapi.json | Контракт OpenAPI 3.1 |
/.well-known/api-catalog | Набор ссылок по RFC 9727 |
/.well-known/mcp/server-card.json | Карточка MCP-сервера |
/index.md | Главная в markdown (также через Accept: text/markdown) |
/robots.txt | Правила обхода |
/privacy.html | Политика конфиденциальности |
/terms.html | Условия использования |
Ссылки на покупку партнёрские (affiliate): цена для покупателя не меняется, а Perkusai получает комиссию.
Вопросы по интеграции: [email protected]