API для разработчиков
Управляйте магазином на «Океано» из своей программы: товары, остатки, заказы, покупатели. О событиях магазина сообщают вебхуки.
Адрес API: https://api.okeysait.ru/v1 · OpenAPI 3.1 · JSON
Начать за 3 шага
-
Создайте ключ
В панели магазина: «Настройки» → «API и вебхуки» → вкладка «Ключи». Назовите ключ, отметьте нужные права и нажмите «Создать ключ». Ключ
oks_…показывается один раз — сохраните его. -
Проверьте ключ
Запрос
GET /shopнужен любому ключу и возвращает данные магазина.curl -H "Authorization: Bearer oks_…" https://api.okeysait.ru/v1/shop{ "site": "myshop", "name": "Мой магазин", "host": "myshop.okeysait.ru", "url": "https://myshop.okeysait.ru", "currency": "RUB", "plan": "free", "limits": {"requests_per_minute": 60}, "key": {"name": "Склад", "scopes": ["products:read", "products:write"]}, "scopes": ["products:read", "products:write"] } -
Вызывайте методы
Ключ передаётся в каждом запросе заголовком
Authorization: Bearer oks_…. Все методы — в таблице ниже.
Ключ — только для сервера. Запросы из браузера не разрешены (CORS выключен): ключ, лежащий в странице, видят все. Для витрины в браузере есть Storefront API без ключа.
На самом okeysait.ru ключ с правом orders:write выдаёт только сотрудник КОН: оплаченный заказ там подключает тариф. Владелец магазина получит 403 staff_only.
Формат
- JSON в UTF-8, поля в
snake_case. - Деньги — строкой с двумя знаками:
"1290.00", валютаRUB. Даты — ISO 8601 с поясом. - Списки —
{"data": […], "next_cursor": "…"}. Параметрыlimit(1–250, по умолчанию 50) иcursor: передайтеnext_cursorиз ответа, чтобы получить следующую страницу.null— страниц больше нет. - Ошибки —
{"error": {"code": "…", "message": "…"}}. Ориентируйтесь наcode.
Методы
Право ключа выбирается в панели. GET /shop открыт любому действующему ключу.
| Метод | Путь | Право | Что делает |
|---|---|---|---|
| GET | /shop | любое | Магазин, тариф, права ключа |
| GET | /products | products:read | Список товаров. Фильтры: category_id, sku, visible, updated_since |
| POST | /products | products:write | Создать товар с вариантами, разделами и фото |
| GET | /products/{id} | products:read | Товар |
| PATCH | /products/{id} | products:write | Изменить товар: только переданные поля |
| DELETE | /products/{id} | products:write | Удалить товар |
| PATCH | /stock | products:write | Остатки и цены пачкой по артикулу, до 500 позиций |
| GET | /categories | products:read | Список разделов |
| POST | /categories | products:write | Создать раздел |
| GET | /categories/{id} | products:read | Раздел |
| PATCH | /categories/{id} | products:write | Изменить раздел |
| GET | /brands | products:read | Список брендов |
| POST | /brands | products:write | Создать бренд |
| GET | /brands/{id} | products:read | Бренд |
| PATCH | /brands/{id} | products:write | Изменить бренд |
| GET | /orders | orders:read | Список заказов (с контактами покупателя — персональные данные). Фильтры: status, paid, updated_since |
| POST | /orders | orders:write | Создать заказ (с Idempotency-Key) |
| GET | /orders/{id} | orders:read | Заказ (с контактами покупателя — персональные данные) |
| PATCH | /orders/{id} | orders:write | Статус, оплата, заметка менеджера, комментарий |
| GET | /customers | customers:read | Список покупателей (персональные данные) |
| GET | /customers/{id} | customers:read | Покупатель |
Что важно знать
- Варианты товара. Цена, артикул и остаток — у варианта.
"stock": null— склад не ведётся, количество не ограничено. Остаток — целое от 0 до 1 000 000 000, цена — от 0 до 1 000 000 000. - Списки заменяются целиком. В
PATCH /products/{id}списокvariantsзаменяет прежний: вариант сidобновляется, безidсоздаётся, отсутствующие в списке удаляются. Пустойvariants: []не принимается. Так же работаютcategory_idsиimages(фото передаются ссылками). - Разделы и бренды.
slug— собственная часть адреса («svechi»); полный адрес с родителями магазин строит сам. Родитель не найден или создаёт цикл —bad_parent. Удаление — только в панели. - Остатки пачкой.
PATCH /stock, до 500 позиций{sku, stock, price, compare_price}. Ответ по каждой позиции:updated,not_found,bad_skuилиnothing_to_update. - Забирать только новое. У товаров и заказов есть
updated_since— время прошлого опроса в ISO 8601.
Заказы
- Без дублей. Передайте заголовок
Idempotency-Key(до 200 символов, ключ помнится 24 часа). Повтор с тем же телом вернёт тот же ответ с заголовкомIdempotent-Replayed: true; с другим телом —422 idempotency_mismatch; пока первый запрос выполняется —409 idempotency_conflict. - Позиции — по
variant_idилиsku,quantityот 1 до 9999 (иначеbad_quantity), не больше 100 позиций. Нужно имя, телефон или почта покупателя. - Оплата.
"paid": trueзакрывает заказ и списывает остатки, как оплата на сайте. Если товара не хватает —409 insufficient_stock, ничего не создано и не изменено. Снять оплату нельзя (unpay_not_supported). - Уведомления. Объект
notify:admin(по умолчаниюtrue) — владелец магазина получает обычное письмо и сообщение о новом заказе;customer(по умолчаниюfalse) — письмо покупателю, если указана его почта. - Заметка менеджера — поле
manager_noteвPATCH; покупателю она не видна.
Примеры
В примерах ключ лежит в переменной окружения OKEANO_KEY.
Создать товар
curl -X POST https://api.okeysait.ru/v1/products \
-H "Authorization: Bearer $OKEANO_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "Свеча «Лес»",
"category_ids": [3],
"images": ["https://example.com/forest.jpg"],
"variants": [{"sku": "CANDLE-F", "price": "1290.00", "stock": 25}]
}'Обновить остатки пачкой
curl -X PATCH https://api.okeysait.ru/v1/stock \
-H "Authorization: Bearer $OKEANO_KEY" \
-H "Content-Type: application/json" \
-d '{"items": [
{"sku": "CANDLE-F", "stock": 18},
{"sku": "CANDLE-S", "price": "990.00", "compare_price": "1290.00"}
]}'{"data": [
{"sku": "CANDLE-F", "result": "updated", "variants": 1},
{"sku": "CANDLE-S", "result": "not_found"}
]}Получить новые заказы
Запомните время начала опроса и в следующий раз передайте его в updated_since. Знак «+» в часовом поясе в адресе кодируйте как %2B (--data-urlencode в curl делает это сам) или пишите время в UTC с буквой Z. Если в ответе next_cursor не null, повторите запрос с cursor.
curl -G https://api.okeysait.ru/v1/orders \
-H "Authorization: Bearer $OKEANO_KEY" \
--data-urlencode "updated_since=2026-10-30T09:00:00+03:00" \
-d limit=100Создать заказ без риска дубля
curl -X POST https://api.okeysait.ru/v1/orders \
-H "Authorization: Bearer $OKEANO_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: crm-order-81234" \
-d '{
"customer": {"name": "Анна Лесная", "phone": "+79990001122", "email": "anna@example.com", "address": "Москва, ул. Лесная, 1"},
"items": [{"sku": "CANDLE-F", "quantity": 2}],
"comment": "Позвоните за час",
"notify": {"admin": true, "customer": false}
}'Ответ 201 — заказ в том же виде, что GET /orders/{id}. Если связь оборвалась, повторите запрос с тем же Idempotency-Key: второй заказ не появится.
Вебхуки
Подключите адрес в панели: «Настройки» → «API и вебхуки» → вкладка «Вебхуки». Укажите адрес https://…, отметьте события и нажмите «Добавить». Секрет whsec_… для проверки подписи показывает кнопка «Секрет». Кнопка «Проверить» отправляет тестовое событие ping, результат виден на вкладке «Журнал».
Что приходит
POST с JSON-телом: номер события, тип, время, магазин и объект в том же виде, что в API. В примере объект сокращён: у заказа в data все поля, как в GET /orders/{id}.
{
"id": "evt_1042",
"type": "order.paid",
"created_at": "2026-10-30T09:31:07+00:00",
"shop": {"site": "myshop", "host": "myshop.okeysait.ru"},
"data": {
"id": 518,
"number": "518",
"paid": true,
"total": "2580.00"
}
}| Заголовок | Значение |
|---|---|
X-Okeano-Event | Тип события, например order.paid |
X-Okeano-Delivery | Номер доставки |
X-Okeano-Signature | Подпись t=<unix>,v1=<hex> — как проверить |
Content-Type | application/json |
User-Agent | Okeano-Webhooks/1.0 |
Как работает доставка
- Ответьте кодом 2xx за 10 секунд — доставка засчитана. Любой другой ответ или тишина — неудача. Редиректы не выполняются.
- Повторы: через 1 минуту, 5 минут, 30 минут, 2 часа, 6 часов и 12 часов. Всего 7 попыток, потом доставка помечается «не доставлено»; на вкладке «Журнал» её можно повторить вручную.
- 50 неудач подряд выключают вебхук, владельцу приходит письмо. Когда адрес снова принимает запросы, нажмите «Включить» в панели.
- «Хотя бы один раз». Одно событие может прийти дважды — отбрасывайте повторы по
id. При смене статуса приходят оба события —order.status_changedиorder.updated; при оплате кorder.paidмогут добавитьсяorder.updatedиorder.status_changed. Отсекайте повторы по id заказа иupdated_at. Порядок событий не гарантируется: не рассчитывайте, чтоorder.paidпридёт послеorder.created. - Массовые изменения. Больше 200 изменений каталога за одну операцию (или загрузка файлом) дают одно событие
catalog.importedвместо события на каждый товар. Если вы подписаны на события товаров или остатков,catalog.importedдобавляется автоматически. После него перечитайте каталог черезGET /products?updated_since=…. - Созданное и откатанное. Событие
*.createdдля объекта, которого уже нет (например, заказ отменён из-за нехватки товара), не отправляется. - Адрес получателя: только
https, порт 443 или 8443, внутренние адреса (localhost, 10.x, 192.168.x и подобные) запрещены. Адрес проверяется при сохранении и перед каждой отправкой.
События
| Событие | Когда приходит, и что в data |
|---|---|
order.created | Создан заказ (сайт, embed.js или API). Заказ с позициями. |
order.paid | Заказ оплачен. Заказ. Приходит при оплате всегда; вместе с ним могут прийти order.updated и order.status_changed — отсекайте повторы по id заказа и updated_at. |
order.status_changed | Сменился статус. Заказ. Вместе с ним приходит и order.updated; при оплате (например, через Robokassa) может прийти вместе с order.paid. |
order.updated | Изменены данные заказа: статус, покупатель, адрес, заметка, состав. Заказ. Если вы подписаны на order.created, для заказа, созданного в том же запросе, order.updated не приходит. |
product.created | Создан товар. Товар. |
product.updated | Изменены товар, варианты, разделы или фото. Товар. |
product.deleted | Товар удалён. {id, skus}. |
stock.changed | Изменились остаток или цена вариантов. {items, truncated}: до 200 вариантов с variant_id, product_id, sku, price, compare_price, stock. Больше 200 изменений за запрос приходят одним catalog.imported; truncated всегда false (оставлено для совместимости). |
catalog.imported | Массовое изменение каталога. {source, counts}: сколько товаров создано, изменено, удалено и сколько остатков изменено. |
customer.created | Зарегистрирован покупатель. Покупатель. |
feedback.created | Сообщение из формы обратной связи. id, name, email, phone, message, created_at. |
ping | Проверка из панели. {message}. |
Проверка подписи
Заголовок X-Okeano-Signature выглядит так: t=1761816667,v1=5f3c…. Подпись v1 — HMAC-SHA256 от строки <t>.<тело> с вашим секретом whsec_…, в hex. Считайте её по сырому телу запроса, а не по разобранному JSON. Отклоняйте запросы, у которых t отличается от текущего времени больше чем на 5 минут.
// PHP: проверка подписи вебхука Океано
function okeano_webhook_valid($body, $header, $secret, $tolerance = 300) {
if (!preg_match('/^t=(\d+),v1=([0-9a-f]{64})$/', $header, $m)) return false;
if (abs(time() - (int)$m[1]) > $tolerance) return false;
return hash_equals(hash_hmac('sha256', $m[1] . '.' . $body, $secret), $m[2]);
}
$ok = okeano_webhook_valid(
file_get_contents('php://input'),
$_SERVER['HTTP_X_OKEANO_SIGNATURE'] ?? '',
'whsec_…'
);// Node.js: проверка подписи вебхука Океано (тело — сырые байты запроса)
const crypto = require("crypto");
function okeanoWebhookValid(rawBody, header, secret, tolerance = 300) {
const m = /^t=(\d+),v1=([0-9a-f]{64})$/.exec(header || "");
if (!m || Math.abs(Date.now() / 1000 - Number(m[1])) > tolerance) return false;
const want = crypto.createHmac("sha256", secret)
.update(`${m[1]}.${rawBody}`)
.digest("hex");
return crypto.timingSafeEqual(Buffer.from(want), Buffer.from(m[2]));
}Если подпись не сошлась, ответьте 400: доставка повторится позже. После проверки отвечайте быстро, а тяжёлую работу выполняйте в фоне.
Лимиты и ошибки
| Что | Бесплатный тариф | Платные и свои сайты |
|---|---|---|
| Ключей API | 1 | 10 |
| Вебхуков | 1 | 20 |
| Запросов в минуту на ключ | 60 | 600 |
Сверх лимита — 429 rate_limited и заголовок Retry-After: через сколько секунд можно повторять. Товаров и разделов столько, сколько позволяет тариф магазина: больше — products_limit и categories_limit.
Коды ошибок
Тело ответа: {"error": {"code": "not_found", "message": "Не найдено"}}. Текст message может уточняться, программе нужен code.
| Код | Что значит |
|---|---|
bad_json400 | Тело запроса — не JSON |
bad_cursor400 | Неверный cursor |
bad_date400 | Неверная дата в updated_since, нужен ISO 8601 |
missing_key401 | Нужен заголовок Authorization: Bearer oks_… |
invalid_key401 | Ключ не найден или отозван |
insufficient_scope403 | У ключа нет права на это действие |
not_found404 | Не найдено: нет объекта или пути |
method_not_allowed405 | Метод для этого пути не поддерживается |
products_limit409 | Достигнут лимит товаров тарифа |
categories_limit409 | Достигнут лимит разделов тарифа |
insufficient_stock409 | Не хватает товара на складе для оплаты заказа |
idempotency_conflict409 | Запрос с этим Idempotency-Key ещё выполняется |
name_required422 | Нужно название |
variants_required422 | Нужен хотя бы один вариант |
bad_variants422 | Неверный список вариантов или позиций |
variant_not_found422 | Вариант не найден или принадлежит другому товару |
bad_price422 | Цена должна быть числом от 0 до 1 000 000 000 |
bad_stock422 | Остаток — целое число или null |
bad_images422 | Фото — список ссылок http(s) |
bad_categories422 | Неверный список разделов |
bad_brand422 | Нет такого бренда |
bad_parent422 | Родительский раздел не найден или создаёт цикл |
items_required422 | Нужен непустой список items |
too_many_items422 | Слишком много позиций за один запрос (остатки — 500, заказ — 100) |
customer_required422 | Нужно имя, телефон или почта покупателя |
bad_email422 | Неверная почта |
bad_quantity422 | Количество — целое число от 1 до 9999 |
bad_status422 | Нет такого статуса заказа |
unpay_not_supported422 | Снять отметку оплаты через API нельзя |
idempotency_mismatch422 | Этот Idempotency-Key уже использован с другим запросом |
order_failed422, 500 | Не удалось создать заказ; частично созданного заказа не остаётся |
rate_limited429 | Слишком много запросов, повторите через минуту |
brand_failed500 | Не удалось создать бренд |
internal_error500 | Внутренняя ошибка, попробуйте позже |
site_unavailable503 | Магазин недоступен |
service_unavailable503 | Сервис временно недоступен |
Адрес вебхука проверяется при сохранении в панели, возможны ещё коды: bad_url (неверный адрес), https_required (только https://), bad_port (порт 443 или 8443), host_not_found (адреса нет в DNS), private_address (внутренние адреса запрещены).
Для браузера: Storefront API
Ключ API нельзя класть в страницу. Чтобы показать магазин на чужом сайте, подключите готовый скрипт — витрина, карточка товара и оформление заказа работают без ключа.
<div id="shop"></div>
<script src="https://<ваш-магазин>/embed.js" data-target="#shop" async></script>Тот же JSON-интерфейс доступен напрямую, в том числе из браузера (CORS включён):
GET https://<магазин>/ajax/embed.php?action=catalog
GET https://<магазин>/ajax/embed.php?action=product&id=…
POST https://<магазин>/ajax/embed.php?action=orderПодробнее о способах запуска — на странице «CMS для интернет-магазина».
Описание OpenAPI
Полное описание методов, объектов и событий — в файле OpenAPI 3.1. Импортируйте его в Postman, Insomnia или Swagger UI либо сгенерируйте клиент для своего языка.