Документация API

Базовый адрес: https://priceapi.ru. Все ответы — JSON в UTF-8, имена полей в snake_case, время в UTC (ISO 8601).

Начало работы

  1. Зарегистрируйтесь и выберите тариф в разделе «Тариф и оплата».
  2. Создайте ключ в разделе «Ключи» — он показывается один раз, сохраните его.
  3. Отправьте запрос со ссылкой на страницу товара.

Авторизация

Ключ передаётся в заголовке Authorization: Bearer pa_… или X-Api-Key: pa_…. Не передавайте ключ в адресе запроса и не встраивайте его в код, который выполняется в браузере ваших пользователей.

Цена по ссылке

GET /v1/price?url=…
ПараметрОписание
urlСсылка на страницу товара, http или https, до 2000 символов. Обязательный. Метки utm_*, yclid, gclid и т. п. отбрасываются — ссылки с разными метками считаются одной страницей.
max_ageСколько секунд сохранённый результат считается свежим. По умолчанию 3600 (час), минимум 300, максимум 604800 (неделя). Если ссылку кто-то запрашивал недавно, ответ придёт сразу из кеша.
waitСколько секунд ждать загрузки новой страницы, от 0 до 120, по умолчанию 60. Не дождались — ответ 202.

То же самое можно отправить методом POST /v1/price с телом {"url": "…", "max_age": 3600, "wait": 60} и заголовком Content-Type: application/json — удобно для длинных ссылок.

Ответ

200 OK — цена найдена или страница сообщает, что товара нет в наличии:

{
  "url": "https://shop.example.ru/product/12345",
  "price": 12990.00,
  "currency": "RUB",
  "in_stock": true,
  "title": "Смартфон …",
  "source": "jsonld",
  "checked_at": "2026-10-31T20:15:04Z",
  "cached": false
}
ПолеОписание
urlСсылка после нормализации: без меток и фрагмента, параметры отсортированы.
priceТекущая цена для покупателя, число. null, если товара нет в наличии и страница не показывает цену.
currencyКод валюты ISO 4217 (RUB, USD, …) или null, если магазин его не указал.
in_stocktrue — можно купить, false — нет в наличии или только предзаказ, null — страница об этом не сообщает.
titleНазвание товара со страницы.
sourceОткуда взята цена: jsonld, microdata, meta — разметка страницы; vision — распознана по скриншоту и сверена с текстом страницы.
checked_atКогда страница была загружена.
cachedtrue, если ответ взят из сохранённой загрузки не старше max_age.

Долгая загрузка: 202

Если страница не загрузилась за wait секунд, API отвечает 202 Accepted с заголовком Retry-After, а загрузка продолжается. Повторите тот же запрос через несколько секунд — он подхватит идущую загрузку или вернёт её результат. Ответ 202 в лимит не засчитывается.

{
  "url": "https://shop.example.ru/product/12345",
  "status": "pending",
  "message": "Страница загружается, повторите тот же запрос через несколько секунд",
  "retry_after": 10
}

Ошибки

Ответ с ошибкой содержит объект error с машинным кодом и описанием:

{
  "url": "https://shop.example.ru/product/12345",
  "error": {
    "code": "no_price",
    "message": "No structured price on the page …",
    "page_title": "Доступ ограничен"
  },
  "checked_at": "2026-10-31T20:15:04Z",
  "cached": false
}
HTTPКодЧто случилось
400invalid_url, invalid_jsonНет ссылки, она не http(s) или тело POST не JSON.
401invalid_keyНет ключа, ключ неверный или отозван.
402no_subscriptionНет оплаченного тарифа или срок оплаты закончился.
429rate_limitedПревышен лимит запросов в минуту; через сколько секунд повторить — в заголовке Retry-After.
429quota_exceededОтветы тарифа на этот месяц закончились.
422not_loadedСтраница не загрузилась (сайт недоступен или отдал ошибку).
422too_largeСтраница слишком большая.
422no_answerЗагрузка не завершилась за отведённое время.
422no_price, price_not_in_text, no_screenshot, model_failedСтраница загрузилась, но цену на ней найти не удалось. Посмотрите page_title: часто это страница защиты от ботов или «товар не найден».
503unavailableЗагрузка страниц временно недоступна, повторите позже.

Ошибка по одной и той же ссылке сохраняется на 10 минут: повторный запрос в это время вернёт ту же ошибку без новой загрузки.

Остаток лимита

GET /v1/account

Тариф, оплаченный период и сколько ответов осталось в текущем месяце. Этот запрос в лимит не засчитывается.

{
  "plan": "business",
  "active": true,
  "paid_until": "2026-12-01T10:00:00Z",
  "month_start": "2026-11-01T10:00:00Z",
  "month_end": "2026-12-01T10:00:00Z",
  "requests_limit": 50000,
  "requests_used": 1234,
  "requests_left": 48766,
  "per_minute": 120
}

Лимиты и учёт

  • В месячный лимит засчитываются только ответы 200 — с ценой или с «нет в наличии», в том числе из кеша.
  • Ответы 202, 4xx и 5xx не засчитываются.
  • Месяц считается от даты оплаты. При оплате того же тарифа заранее оплаченный срок продлевается на месяц, при смене тарифа новый тариф начинает действовать сразу.
  • Лимит в минуту считается по всем ключам аккаунта вместе.

Примеры

curl -G "https://priceapi.ru/v1/price" \
  -H "Authorization: Bearer pa_ваш_ключ" \
  --data-urlencode "url=https://shop.example.ru/product/12345"
import time
import requests

def get_price(url):
    while True:
        r = requests.get("https://priceapi.ru/v1/price",
                         params={"url": url},
                         headers={"Authorization": "Bearer pa_ваш_ключ"},
                         timeout=130)
        if r.status_code == 202:  # страница ещё загружается
            time.sleep(int(r.headers.get("Retry-After", 10)))
            continue
        return r.status_code, r.json()

status, data = get_price("https://shop.example.ru/product/12345")
if status == 200:
    print(data["price"], data["currency"], data["in_stock"])
else:
    print(data["error"]["code"], data["error"]["message"])
// Node.js 18+ или браузер (ключ не стоит отдавать в браузер пользователям)
async function getPrice(url) {
  const api = new URL("https://priceapi.ru/v1/price");
  api.searchParams.set("url", url);
  for (;;) {
    const r = await fetch(api, { headers: { Authorization: "Bearer pa_ваш_ключ" } });
    if (r.status === 202) {
      await new Promise(ok => setTimeout(ok, 10_000));
      continue;
    }
    return { status: r.status, data: await r.json() };
  }
}

const { status, data } = await getPrice("https://shop.example.ru/product/12345");
console.log(status, data.price, data.currency, data.in_stock);
<?php
function getPrice(string $url): array {
    $api = "https://priceapi.ru/v1/price?" . http_build_query(["url" => $url]);
    do {
        $ch = curl_init($api);
        curl_setopt_array($ch, [
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 130,
            CURLOPT_HTTPHEADER => ["Authorization: Bearer pa_ваш_ключ"],
        ]);
        $body = curl_exec($ch);
        $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
        curl_close($ch);
        if ($status == 202) sleep(10);
    } while ($status == 202);
    return [$status, json_decode($body, true)];
}

[$status, $data] = getPrice("https://shop.example.ru/product/12345");
if ($status == 200) echo $data["price"], " ", $data["currency"];
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Text.Json;

var http = new HttpClient { Timeout = TimeSpan.FromSeconds(130) };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "pa_ваш_ключ");

var api = "https://priceapi.ru/v1/price?url=" + Uri.EscapeDataString("https://shop.example.ru/product/12345");
HttpResponseMessage r;
while ((r = await http.GetAsync(api)).StatusCode == System.Net.HttpStatusCode.Accepted)
    await Task.Delay(TimeSpan.FromSeconds(10));

var data = await r.Content.ReadFromJsonAsync<JsonElement>();
if (r.IsSuccessStatusCode)
    Console.WriteLine($"{data.GetProperty("price")} {data.GetProperty("currency")}");
Ссылка = "https://shop.example.ru/product/12345";
Соединение = Новый HTTPСоединение("priceapi.ru", 443, , , , 130,
    Новый ЗащищенноеСоединениеOpenSSL);
// wait=120: ждать загрузки страницы до двух минут
Запрос = Новый HTTPЗапрос("/v1/price?wait=120&url=" + КодироватьСтроку(Ссылка, СпособКодированияСтроки.КодировкаURL));
Запрос.Заголовки.Вставить("Authorization", "Bearer pa_ваш_ключ");

Ответ = Соединение.Получить(Запрос);

Чтение = Новый ЧтениеJSON;
Чтение.УстановитьСтроку(Ответ.ПолучитьТелоКакСтроку());
Данные = ПрочитатьJSON(Чтение);
Если Ответ.КодСостояния = 200 Тогда
    Сообщить("Цена: " + Данные.price + " " + Данные.currency);
ИначеЕсли Ответ.КодСостояния = 202 Тогда
    Сообщить("Страница ещё загружается, повторите запрос через минуту");
Иначе
    Сообщить(Данные.error.message);
КонецЕсли;