SkyStream API

Ошибки

Формат ошибки, классы и полный перечень кодов.

Ошибка возвращается с кодом ответа HTTP от 400 и объектом error в теле:

Ответ400viewers_below_minimum
{  "error": {    "type": "invalid_request",    "code": "viewers_below_minimum",    "message": "Зрителей на канале — не меньше 15.",    "param": "viewers",    "request_id": "req_2f9cA1bQ7xLm0Rk4TzP8sWd3"  }}
ПолеЧто содержит
typeКласс ошибки. По нему удобно решать, повторять ли запрос
codeТочная причина. Код не меняет смысла между выпусками — ветвите обработку по нему
messageОписание для человека. Текст может уточняться, не разбирайте его программно
paramПоле запроса, к которому относится ошибка. Есть не у всех ошибок
request_idИдентификатор запроса, тот же, что в заголовке X-Request-Id. В сохранённом ответе на повтор с Idempotency-Key — идентификатор первого запроса

Классы

typeHTTPЧто делать
authentication401Проверить ключ. Повтор с тем же ключом не поможет
permission403Изменить права или настройки ключа в панели
invalid_request400, 402, 413, 415, 422Исправить запрос
not_found404Проверить идентификатор и адрес
conflict409Объект не в том состоянии: например, запуск уже идёт. Прочитать объект и решить заново
rate_limit429Повторить запрос через время из заголовка Retry-After
internal500, 502, 503, 504Повторить запрос позже. Если ошибка повторяется, сообщить request_id в поддержку

Повторяя изменяющий запрос после ошибки класса internal, передавайте тот же Idempotency-Key: так действие не выполнится второй раз, если первый запрос всё-таки прошёл. Подробнее — в разделе Идемпотентность.

Ошибки при списании

Ошибки, связанные со списанием, — insufficient_funds, daily_spend_limit_exceeded, order_not_started — означают, что деньги не списаны или уже возвращены. Интеграции не нужно проверять баланс после такой ошибки.

Обработка ошибок

Пример клиента на JavaScript (Node.js 18 и новее). Он повторяет то, что имеет смысл повторять, и передаёт наверх остальное:

  • превышение частоты — после паузы из Retry-After;
  • сбои и обрыв связи — с нарастающей паузой;
  • изменяющие запросы — всегда с Idempotency-Key: ключ создаётся один раз и одинаков во всех попытках, поэтому повтор не выполнит действие второй раз.
import { randomUUID } from "node:crypto";

const API_BASE = "https://api.skystream.su/app/v1";
const MAX_ATTEMPTS = 5;

const sleep = (seconds) => new Promise((resolve) => setTimeout(resolve, seconds * 1000));

export class ApiError extends Error {
  constructor(status, error) {
    super(error.message);
    this.status = status;
    this.type = error.type;
    this.code = error.code;
    this.param = error.param;
    this.requestId = error.request_id;
  }
}

/** Повторять ли запрос после такой ошибки. */
function retryable(error, method) {
  if (error.type === "rate_limit") return true;
  // Первый запрос с этим ключом ещё выполняется: дождаться его ответа.
  if (error.code === "idempotency_key_in_progress") return true;
  if (error.type !== "internal") return false;
  // internal_error изменяющего запроса сохранён под ключом: повтор вернёт его же.
  // Проверьте чтением, выполнилось ли действие.
  return method === "GET" || error.code !== "internal_error";
}

export async function api(method, path, body) {
  const idempotencyKey = method === "GET" ? null : randomUUID();

  for (let attempt = 1; ; attempt++) {
    const last = attempt === MAX_ATTEMPTS;
    let response;
    try {
      response = await fetch(`${API_BASE}${path}`, {
        method,
        headers: {
          Authorization: `Bearer ${process.env.API_KEY}`,
          ...(idempotencyKey && { "Idempotency-Key": idempotencyKey }),
          ...(body && { "Content-Type": "application/json" }),
        },
        body: body && JSON.stringify(body),
      });
    } catch (networkError) {
      // Ответа нет: запрос мог выполниться. С тем же ключом повтор безопасен.
      if (last) throw networkError;
      await sleep(2 ** attempt);
      continue;
    }

    if (response.ok) return response.json();

    const { error } = await response.json();
    if (last || !retryable(error, method)) throw new ApiError(response.status, error);
    await sleep(Number(response.headers.get("Retry-After")) || 2 ** attempt);
  }
}

Остальные ошибки обрабатываются по коду:

try {
  const order = await api("POST", "/orders/views", { channel: "example_channel", views: 1000 });
  console.log(`Заказ ${order.id}: списано ${order.price.amount / 100} ₽`);
} catch (error) {
  if (!(error instanceof ApiError)) throw error;
  switch (error.code) {
    case "insufficient_funds":
    case "daily_spend_limit_exceeded":
      // Деньги не списаны. Повтор не поможет: нужно пополнить счёт или поднять потолок.
      break;
    case "quantity_out_of_range":
      // Пределы количества — в GET /prices.
      break;
    default:
      // Неизвестный код — по классу error.type. request_id — для поддержки.
      console.error(`${error.code}: ${error.message} (${error.requestId})`);
  }
}

Перечень кодов

Коды сгруппированы по классам. Новые коды могут появляться в рамках версии; обрабатывайте неизвестный код по его классу.

Ключ и доступ

КодHTTPЧто значит
api_key_expired401Срок ключа истёк. Выпустите новый ключ в панели.
api_key_invalid401Ключ не найден или передан не целиком. Запросы с неверным ключом считаются: после 20 за 10 минут адрес временно блокируется (too_many_invalid_keys).
api_key_missing401Не передан ключ. Передайте его в заголовке Authorization: Bearer <ключ>.
api_key_revoked401Ключ отозван. Выпустите новый ключ в панели.
api_key_wrong_product401Ключ выпущен для другого раздела API.
КодHTTPЧто значит
account_locked403Доступ к панели для аккаунта закрыт. Ключи аккаунта не действуют, пока доступ не восстановлен.
api_access_disabled403Доступ к API для аккаунта выключен. Включает его поддержка.
api_unavailable403API для этого аккаунта недоступен.
app_only403У аккаунта нет личного лимита зрителей, а раздел работает только с ним.
daily_spend_limit_exceeded403Заказ превысил бы суточный потолок трат ключа. Ничего не списано. Потраченное за сутки — в key.spent_today ответа GET /account; сутки считаются по московскому времени.
insufficient_scope403У ключа нет права, которого требует запрос. Права ключа меняются в панели; расширение прав подтверждается кодом.
ip_not_allowed403Запрос пришёл с адреса, которого нет в списке IP ключа. Список меняется в панели.
service_disabled403Услуга временно недоступна.

Ошибки в запросе

КодHTTPЧто значит
api_key_in_query400Ключ передан в строке запроса. Запрос отклоняется, даже если ключ верный: строка запроса сохраняется в журналах. Передавайте ключ только в заголовке и замените этот ключ в панели — его следует считать раскрытым.
auto_views_panel_only400Автопросмотры включаются только в панели: они оплачиваются поминутно, мимо суточного потолка ключа. Через API их можно только выключить.
channel_limit_reached400Заведено наибольшее число каналов. Удалите ненужный канал.
channel_not_on_platform400Канала с таким именем на площадке нет.
choice_not_found400Варианта нет в текущем опросе. Варианты — в GET /chat-panels/{id}/poll.
country_unavailable400Страны нет в списке доступных. Список — в available.geo_countries настроек канала.
duration_above_cap400Срок больше наибольшего срока одного запуска.
duration_not_supported400Срок запуска задаётся только у разового канала.
duration_out_of_range400Срок вне пределов услуги. Пределы — в GET /prices.
graph_limit_reached400Создано наибольшее число графиков (max_graphs в GET /account). Удалите ненужный график.
graph_not_found400Графика с таким id нет у аккаунта. Графики — в GET /graphs.
https_required400Запрос отправлен по HTTP. API принимает только HTTPS и на HTTP не перенаправляет. Ключ, отправленный по HTTP, мог быть перехвачен: отзовите его в панели и выпустите новый.
idempotency_key_invalid400Idempotency-Key — строка от 8 до 128 знаков из латинских букв, цифр и символов _ : . -.
idempotency_key_missing400Запрос списывает деньги, и заголовок Idempotency-Key для него обязателен.
interval_out_of_range400Пауза между сообщениями вне пределов услуги. Пределы — в GET /prices.
invalid_body400Тело запроса не того вида: например, массив вместо объекта.
invalid_channel_name400Имя канала не подходит для площадки.
invalid_json400Тело запроса — не JSON.
limit_expired400Срок лимита истёк. До продления нельзя заводить каналы, повышать их потолок и запускать зрителей; снижать потолок, удалять каналы и менять настройки можно.
limit_not_granted400Аккаунту не выдан лимит зрителей.
limit_too_small400Лимит аккаунта меньше наименьшего потолка канала.
nothing_to_change400Запрос на изменение не содержит ни одного изменения.
order_not_cancellable400Отменить можно только чат-ботов и чат-панель.
order_not_extendable400Продлить можно только чат-ботов и чат-панель.
outcome_not_found400Исхода нет в текущем предсказании. Исходы — в GET /chat-panels/{id}/prediction.
parameter_invalid400Значение поля не подходит. Поле — в param, причина — в message.
parameter_missing400Не передано обязательное поле. Его имя — в param.
parameter_unknown400Передано поле, которого у запроса нет. Опечатка в имени поля не проходит молча: поле — в param.
platform_not_supported400Возможности нет у площадки панели: баллов, наград, опросов и предсказаний нет у Kick, истории чата — у Twitch.
points_votes_disabled400В опросе нельзя голосовать за баллы канала. Голосуйте без points.
quantity_out_of_range400Количество вне пределов услуги. Пределы — в GET /prices.
reply_message_not_found400Kick: сообщения, на которое нужен ответ, нет среди последних сообщений чата. Id берутся из GET /chat-panels/{id}/messages.
reward_text_required400Награда требует текст. Передайте его в text.
run_duration_cap_reached400Запуск уже работает наибольший срок одного запуска, продлевать некуда. Чтобы зрители работали дальше, запустите канал заново после остановки.
run_not_extendable400Продлевается только разовый запуск со сроком окончания.
setting_not_supported400Настройки нет у каналов этой площадки: например, поле Twitch у канала Kick.
setting_unavailable400Возможность сейчас выключена владельцем сервиса.
viewers_above_channel_cap400Число зрителей больше потолка канала.
viewers_above_limit400Потолок канала больше лимита аккаунта.
viewers_below_minimum400Потолок канала меньше наименьшего допустимого.
insufficient_funds402На счёте недостаточно средств. Ничего не списано.
request_too_large413Тело запроса больше 64 КБ.
unsupported_media_type415Тело передано не как application/json.
idempotency_key_reused422Ключ уже использован для другого запроса: другого адреса, строки запроса или тела. Для нового запроса нужен новый ключ.

Объект не найден

КодHTTPЧто значит
account_not_in_panel404Аккаунта с таким логином нет в панели: его могли заменить. Текущий список — в GET /chat-panels/{id}.
resource_missing404Объекта с таким id нет у аккаунта. Чужой объект, несуществующий и id не того вида в адресе запроса дают один и тот же ответ.
route_not_found404Такого адреса в API нет.

Конфликт состояния

КодHTTPЧто значит
active_limit_reached409Работает наибольшее число чат-панелей на этой площадке. Число — в chat_panel.max_active ответа GET /prices.
channel_exists409Канал с этим именем уже заведён на этой площадке.
channel_offline409Twitch: канал не в эфире, и аккаунты панели отключены до начала эфира. Они подключатся сами; status панели в это время — paused.
chat_panel_starting409Панель ещё поднимается на сервере. Повторите запрос через минуту.
graph_in_use409График назначен каналам, и без force=true он не удаляется. Снимите его с каналов (graph_id: null в настройках) или повторите удаление с force=true — назначения снимутся вместе с графиком.
idempotency_key_in_progress409Запрос с этим ключом ещё выполняется. Дождитесь его ответа и повторите запрос с тем же ключом.
insufficient_points409У аккаунта не хватает баллов канала. Баланс — в GET /chat-panels/{id}/accounts?include=points.
no_free_accounts409Нет аккаунтов, которые могут участвовать: все уже проголосовали, стоят на другом исходе или отключены.
order_not_active409Заказ не работает: ещё не запущен, уже завершён или отменён.
panel_state_changed409Состояние панели изменилось во время запроса: например, аккаунт заменили в тот же момент. Повторите запрос.
poll_not_open409На канале нет опроса, который принимает голоса.
prediction_not_open409На канале нет предсказания, которое принимает ставки.
reward_redeem_failed409Площадка отказала в покупке награды; причина — в тексте ошибки. Баллы не списаны.
reward_unavailable409Награду сейчас нельзя купить: её нет в наличии, она на паузе или в откате.
run_already_active409На канале уже есть запуск.
run_not_active409На канале нет запуска.
sending_in_progress409По панели уже идёт раздача голосов или ставок. Дождитесь её конца или остановите её запросом cancel.
sending_not_active409Раздача не идёт: она уже закончилась или её не было.

Частота

КодHTTPЧто значит
rate_limited429Превышен предел частоты запросов. Повторите запрос через время из заголовка Retry-After.
too_many_invalid_keys429С адреса пришло слишком много запросов с неверным ключом. Адрес заблокирован до времени из заголовка Retry-After.

Сбои

КодHTTPЧто значит
internal_error500Внутренняя ошибка. Если запрос шёл с Idempotency-Key, прежде чем повторять его с новым ключом, проверьте, выполнилось ли действие: повтор с тем же ключом получит тот же ответ и действие второй раз не выполнит.
live_apply_failed502Настройки сохранены, но к уже идущим просмотрам не применились. Повторите запрос: он безопасен.
order_not_started502Сервис не принял заказ. Списанная сумма возвращена на счёт и в суточный потолок ключа. Запрос можно повторить с тем же Idempotency-Key.
run_partially_updated502Сервис запусков применил изменение не целиком. Запрос задаёт значения, а не прибавляет их, и повторить его безопасно.
service_error502Сервис запусков или чат-сервис не выполнил запрос. Повторите его позже; если ошибка повторяется, сообщите в поддержку request_id.
chat_panel_unavailable503Заказ работает, но панели сейчас нет на сервере: например, он перезапускается и восстанавливает панели. Повторите запрос через минуту.
platform_unavailable503Площадка не ответила на проверку канала или временно отключена. Повторите запрос позже.
server_unavailable503API временно недоступен: например, идёт обновление. Повторите запрос позже. Запрос мог оборваться уже во время выполнения, поэтому изменяющий запрос повторяйте с тем же Idempotency-Key.
service_unavailable503Сервис запусков или чат-сервис не ответил. Повторите запрос позже.
outcome_unknown504Чат-сервис не ответил вовремя, но действие — сообщение, голоса, ставки, покупка награды — могло выполниться. Повтор выполнил бы его второй раз, в том числе с тем же Idempotency-Key: ключ освобождается при ошибке. Сначала проверьте результат чтением.
server_timeout504API не успел ответить. Запрос мог выполниться: повторите его с тем же Idempotency-Key — действие второй раз не выполнится — или проверьте результат чтением.

На этой странице