Перейти к содержимому
Anon Wallet

API истории доступности RAILGUN

API доступности отвечает на вопрос «что монитор Anon видит сейчас?». Этот эндпоинт отвечает на вопрос «что он видел последние две недели?». Для каждого сервиса и каждой сети из живого отчёта он возвращает до 14 дней данных, по одному символу на 5-минутный интервал, и долю доступности за последние 24 часа, 7 и 14 дней. Этого достаточно, чтобы нарисовать столбчатую диаграмму для страницы статуса. Читать его может любой. API-ключ и аккаунт не нужны.

GET https://api.anon.inc/api/v1/railgun/liveness/history

Это картина, полученная собственным монитором Anon из одного места. Это не официальный статус Railgun и не измерение самого Railgun: каждый символ — это статус, опубликованный монитором Anon. В разделе Ограничения перечислено, чего он показать не может.

Бета: этот API находится в бета-версии, обещания те же, что и у живого эндпоинта. Поля, значения и форма ответа могут измениться без предупреждения. Пишите клиент так, чтобы он игнорировал неизвестные поля и значения. См. Статус беты.

Параметр Значение
Базовый URL https://api.anon.inc
Аутентификация Нет
CORS Любой источник, без учётных данных (GET, HEAD, OPTIONS)
Статус Бета: может измениться без предупреждения (Статус беты)
Формат JSON, schemaVersion 1 (JSON Schema)
Разрешение 5-минутные интервалы, до 14 дней
Кеш 60 секунд в браузерах, 120 секунд в общих кешах

Быстрый старт

curl -i https://api.anon.inc/api/v1/railgun/liveness/history

Ответ содержит историю и заголовки кеширования:

HTTP/2 200
content-type: application/json; charset=utf-8
cache-control: public, max-age=60, s-maxage=120, stale-while-revalidate=60, stale-if-error=300
etag: "5b3f0c9a1d7e4426"
access-control-allow-origin: *
access-control-expose-headers: ETag

Отправьте ETag обратно в If-None-Match, чтобы спросить, изменилось ли что-нибудь. Если нет, ответом будет 304 Not Modified без тела:

curl -i https://api.anon.inc/api/v1/railgun/liveness/history \
  -H 'If-None-Match: "5b3f0c9a1d7e4426"'

Параметров запроса нет. Каждый запрос возвращает всю сохранённую историю, после сжатия это несколько килобайт, потому что строки в основном состоят из одного повторяющегося символа.

Небольшой клиент на TypeScript. Он читает одну строку, сжимает её интервалы до заданного числа столбцов и форматирует долю доступности. null показывается как «нет данных», а не как 100%, а незнакомый символ считается отсутствием данных:

type Uptime = Record<"24h" | "7d" | "14d", number | null>;

interface HistoryRow {
  service: string;
  chainId: number | null; // null for waku
  buckets: string; // one character per bucket, oldest first
  uptime: Uptime;
}

interface History {
  schemaVersion: number;
  generatedAt: string;
  bucketSeconds: number;
  retentionDays: number;
  from: string;
  to: string;
  rows: HistoryRow[];
}

const res = await fetch("https://api.anon.inc/api/v1/railgun/liveness/history", {
  headers: { Accept: "application/json" },
  signal: AbortSignal.timeout(8000),
});
if (!res.ok) throw new Error(`liveness history: HTTP ${res.status}`);
const report: History = await res.json();
if (report.schemaVersion !== 1) throw new Error("liveness history: unknown schemaVersion");

// Worst known state wins. Anything else in a bar is only shown if nothing known is.
const SEVERITY: Record<string, number> = { c: 0, k: 1, d: 2, u: 3 };

function barState(chars: string): string {
  let worst = "";
  for (const ch of chars) {
    if (ch in SEVERITY && (worst === "" || SEVERITY[ch] > SEVERITY[worst])) worst = ch;
  }
  if (worst) return worst;
  if (chars.includes("?")) return "?"; // unknown: no evidence
  if (chars.includes(".")) return "."; // unmonitored
  return "-"; // no data, or a character this client does not know
}

// Split the buckets into `bars` groups of nearly equal size, oldest first. Fewer
// buckets than bars gives one bar per bucket.
function downsample(buckets: string, bars: number): string[] {
  const count = Math.min(bars, buckets.length);
  const out: string[] = [];
  for (let i = 0; i < count; i++) {
    const start = Math.floor((i * buckets.length) / count);
    const end = Math.floor(((i + 1) * buckets.length) / count);
    out.push(barState(buckets.slice(start, end)));
  }
  return out;
}

// The server rounds to 4 decimals, so this is exact. null means "not enough data",
// which is not the same as 0% or 100%.
function formatUptime(value: number | null | undefined): string {
  if (typeof value !== "number" || !(value >= 0 && value <= 1)) return "no data";
  return `${(Math.round(value * 10000) / 100).toFixed(2)}%`;
}

for (const row of report.rows) {
  const label = `${row.service} ${row.chainId ?? "-"}`;
  console.log(label, downsample(row.buckets, 90).join(""), formatUptime(row.uptime["14d"]));
}

Сопоставляйте строки по service и chainId и игнорируйте те, которые вам не нужны. Не полагайтесь на порядок строк.

Эндпоинт

GET https://api.anon.inc/api/v1/railgun/liveness/history

  • Аутентификации и параметров нет. Любая строка запроса игнорируется.
  • HEAD возвращает только заголовки. OPTIONS отвечает на предварительные запросы CORS.
  • Тело ответа — application/json; charset=utf-8.
  • Один ответ охватывает все сервисы во всех сетях. Запросите его один раз и отфильтруйте.
  • Строки те же, что в живом отчёте по адресу /api/v1/railgun/liveness, и идут в том же порядке: сейчас их 21. Что представляет собой каждый сервис, см. в разделе Сервисы и сети.

Пример ответа

Сокращённая история из пяти строк, значения условные. Она охватывает всего два часа — именно такой ответ возвращает эндпоинт сразу после начала записи. Настоящий ответ перечисляет все строки, а его строки buckets вырастают до 4033 символов (Как долго хранится история).

{
  "schemaVersion": 1,
  "generatedAt": "2026-10-06T14:03:11.402Z",
  "bucketSeconds": 300,
  "retentionDays": 14,
  "from": "2026-10-06T12:00:00Z",
  "to": "2026-10-06T14:00:00Z",
  "rows": [
    {
      "service": "ppoi",
      "chainId": 1,
      "buckets": "cccccccccccccccckcccccccc",
      "uptime": { "24h": 0.96, "7d": 0.96, "14d": 0.96 }
    },
    {
      "service": "subsquid",
      "chainId": 137,
      "buckets": "cccccckkkkdddcccccccccccc",
      "uptime": { "24h": 0.72, "7d": 0.72, "14d": 0.72 }
    },
    {
      "service": "subsquid",
      "chainId": 11155111,
      "buckets": ".........................",
      "uptime": { "24h": null, "7d": null, "14d": null }
    },
    {
      "service": "broadcasters",
      "chainId": 137,
      "buckets": "cccccccccccccuccccccccccc",
      "uptime": { "24h": 0.96, "7d": 0.96, "14d": 0.96 }
    },
    {
      "service": "waku",
      "chainId": null,
      "buckets": "cccccc------ccccccccccccc",
      "uptime": { "24h": 1, "7d": 1, "14d": 1 }
    }
  ]
}
  • from — 2026-10-06T12:00:00Z, а to — 2026-10-06T14:00:00Z: 24 промежутка, поэтому каждая строка buckets содержит 25 символов, по одному на интервал, от старого к новому. to — начало последнего интервала, который на момент generatedAt (14:03) ещё заполнялся.
  • В строке ppoi один интервал k: за эти 5 минут сервис был в статусе catching-up не меньше минуты, и ничто худшее не длилось так долго. Доступность — 24 из 25 известных интервалов, то есть 0.96.
  • В строке subsquid для Polygon (137) четыре интервала k, затем три интервала d подряд, после чего сервис восстанавливается. 18 из 25 интервалов — c, поэтому доступность 0.72.
  • Строка subsquid для Sepolia состоит только из .: не отслеживается. Известных интервалов меньше 12, поэтому вся доступность равна null.
  • В строке broadcasters один интервал u: за эти 5 минут сервис был в статусе unavailable не меньше минуты.
  • В строке waku шесть интервалов -: монитор не работал или ничего не записал. Они исключены из доступности, которая равна 1 по 19 известным интервалам.
  • Три значения доступности здесь совпадают, потому что история длится всего два часа. Каждое окно обрезается по имеющимся данным.
  • chainId равен null для waku — единственной общей строки.

Поля

История

Поле Тип Значение
schemaVersion целое число Сейчас 1. В бета-версии может измениться.
generatedAt временная метка Когда сохранённая история была записана в последний раз, а не когда она отдана. Сдвигается вперёд примерно раз в минуту.
bucketSeconds целое число Длина интервала. Сейчас 300.
retentionDays целое число Максимальный срок хранения истории. Сейчас 14.
from временная метка Начало buckets[0], выровненное по границе 300 секунд.
to временная метка Начало последнего интервала, который ещё заполняется.
rows массив По одному объекту на сервис и область, в том же порядке, что и в живом отчёте.

from и to указаны в UTC с буквальным Z и без долей секунды: YYYY-MM-DDTHH:mm:ssZ. У generatedAt точность до миллисекунды, как в живом отчёте.

Число символов в каждой строке buckets равно (to − from) / bucketSeconds + 1. Оно одинаково во всех строках.

Строка

Поле Тип Значение
service строка Сейчас ppoi, indexer, subsquid, broadcasters или waku. Могут появиться другие. Значение то же, что в живом отчёте.
chainId целое число или null Положительный идентификатор сети. Сейчас null для waku, это одна общая проверка.
buckets строка По одному символу на интервал от from до to включительно, от старого к новому.
uptime объект 24h, 7d и 14d, каждое — число от 0 до 1 или null. См. Доступность.

Sepolia (11155111) — тестовая сеть. Как и в живом отчёте, не включайте её в вычисляемые вами показатели основных сетей.

Символы

Символ Опубликованный статус Значение
c current Сервис ответил и был синхронизирован.
k catching-up Сервис ответил, но отставал. Это не сбой.
d degraded Сервис работал с серьёзными проблемами.
u unavailable Монитор Anon не получил пригодного ответа на трёх пробах подряд.
? unknown В этом интервале не было пригодных данных.
. unmonitored Anon не отслеживает эту проверку.
- нет Нет данных: монитор не работал или не записал ни одного замера за этот интервал.

Статусы означают то же, что и в живом отчёте: см. Статус. Этот набор может расшириться. Незнакомый символ считайте - только для этого интервала, а остальную строку сохраните.

Рисуйте ?, . и - тремя разными способами, ни один из которых не должен выглядеть зелёным. Это три разные вещи: нет данных для оценки, не охвачено и не записано.

Как вычисляются интервал и доступность

Интервал

Монитор публикует статус каждой строки примерно раз в 30 секунд. История хранит эти опубликованные статусы, а символ интервала определяется теми из них, которые были опубликованы для этой строки за его 5 минут:

  1. Возьмите известные статусы: current, catching-up, degraded и unavailable; серьёзность убывает в порядке u, d, k, c. Символ соответствует худшему статусу, который в сумме длился не меньше минуты за эти 5 минут. Время в более тяжёлом статусе засчитывается в него: 30 секунд degraded и 40 секунд unavailable дают d. Более короткие сбои, например перезапуск, не учитываются, и интервал показывает c.
  2. Если известных нет, но есть unknown, символ — ?.
  3. Если есть только статусы unmonitored, символ — ..
  4. Если замеров не было вообще, символ — -.

Поэтому u означает, что сервис был в статусе unavailable не меньше минуты в этом интервале, а c означает, что ничто хуже current не длилось минуту. Интервал отражает худший статус, который продержался, а не среднее. Последний интервал ещё заполняется, поэтому его символ может измениться до его окончания.

Доступность

Для каждого окна доступность равна:

c / (c + k + d + u)

Считается по интервалам за последние 24 часа, 7 или 14 дней, с обрезкой по имеющейся истории. Результат округляется до 4 знаков после запятой.

  • Интервалы с ?, . или - исключаются и из числителя, и из знаменателя. Они не считаются ни успехом, ни сбоем.
  • k снижает доступность так же, как d и u. Сервис, который догоняет, не считается работающим.
  • Значение равно null, если в окне меньше 12 известных интервалов: данных меньше чем на час. Для полностью неотслеживаемой строки значение равно null.
  • null — это не 0 и не 1. Показывайте его как «нет данных».
  • Значение ровно 1 означает, что ни один известный интервал в этом окне не был чем-либо, кроме c. Один интервал не-c за 14 дней даёт не более 0.9998.

Вы можете вычислить собственное окно или собственную комбинацию сервисов по buckets. Ваш показатель может немного отличаться от серверного на границах окна.

Как долго хранится история

from — это первый интервал, который монитор когда-либо записал, или начало 14-дневного окна, если оно позже. История существует только с того дня, когда функция заработала, заполнения задним числом нет. Поэтому строки начинаются с одного символа и растут на один каждые 5 минут, пока не достигнут 4033 символов, на что уходят первые 14 дней. После этого окно скользит: когда начинается новый интервал, самый старый отбрасывается.

До этого 24h, 7d и 14d могут описывать один и тот же короткий период, а 7d и 14d у молодой истории могут быть null. Не подписывайте показатель «14 дней», не сказав, сколько данных за ним стоит. Сравните для этого from и to.

Ограничения

  • Одна точка наблюдения. Это монитор Anon, работающий на инфраструктуре Anon и опрашивающий сервисы, которые Anon настроен использовать. Ваша картина этих сервисов может отличаться. Интервал не показывает, что увидели бы ваш кошелёк или ваша сеть.
  • Это то, что опубликовал монитор, а не измерение Railgun. Интервал u означает, что монитор трижды подряд не получил пригодного ответа от сервиса, который он настроен читать. Это не доказывает, что сервис не работал для кого-то ещё. Это также не сигнал «безопасно проводить транзакции»: см. Чего это не показывает.
  • История начинается с запуска. Ничего старше первого записанного интервала не существует, заполнения задним числом нет. Короткая история — это короткая история, а не безупречная.
  • Нет данных — это пробел в мониторинге. Интервал - не является ни сбоем, ни успехом. Он означает, что монитор не работал или ничего не записал за эти 5 минут, поэтому показывайте его как пробел. Доступность его исключает.
  • Неизвестно — тоже не сбой. ? означает, что у монитора не было данных. Доступность его исключает.
  • Только 14 дней. Более старые интервалы удаляются. Если нужна более длинная запись, сохраняйте ответ самостоятельно.
  • Грубое разрешение. 5-минутный интервал скрывает всё, что короче. Проблема, длившаяся в сумме меньше минуты, не видна вовсе, а длившаяся минуту окрашивает весь интервал. Доступность наследует и то и другое.
  • Бета. Как и живой эндпоинт, он может измениться без предупреждения: см. Статус беты.

Актуальность и кеширование

История меняется медленно: самый новый интервал может меняться по мере поступления замеров, а когда история достигнет 14 дней, окно сдвигается на один интервал каждые 5 минут. Ответ содержит:

Cache-Control: public, max-age=60, s-maxage=120, stale-while-revalidate=60, stale-if-error=300
Директива Действие
max-age=60 Браузер использует свою копию 60 секунд.
s-maxage=120 Общий кеш, например CDN, использует свою копию 2 минуты.
stale-while-revalidate=60 Кеш может отдавать копию ещё до минуты после этого, пока запрашивает свежую.
stale-if-error=300 Если источник возвращает ошибку, кеш может продолжать отдавать копию до 5 минут.

Насколько она может устареть? generatedAt показывает, когда сохранённая история была записана в последний раз. Сверх этого копия на периферии к моменту, когда она до вас дойдёт, может быть старше на несколько минут: до 2 минут s-maxage плюс до минуты stale-while-revalidate, а в браузере ещё до минуты. Не используйте этот эндпоинт, чтобы решить, работает ли сервис прямо сейчас. Для этого используйте живой эндпоинт и читайте его expiresAt.

Опрашивайте не чаще раза в минуту. Более частые запросы вернут лишь кешированные копии. Страница с графиком может загрузить данные один раз при открытии и ещё раз, если спустя несколько минут она снова видна.

ETag и 304. ETag — это хеш тела в кавычках. Монитор перезаписывает историю примерно раз в минуту, а generatedAt входит в тело, поэтому generatedAt и ETag меняются примерно раз в минуту, даже если ни один интервал не изменился. Ожидайте 304 в основном при повторной проверке в пределах той же минуты. С помощью If-None-Match неизменившаяся история обходится ответом 304: он содержит заголовки ETag, Cache-Control и CORS и не имеет тела. Ответ не отправляет Vary: Origin. CDN хранит отдельную копию для каждого значения заголовка запроса Origin и игнорирует строку запроса.

Коды состояния HTTP и ошибки

Код Когда Примечания
200 История. 200 означает, что историю можно прочитать, а не что сервисы исправны.
304 If-None-Match совпал. Без тела.
204 Предварительный запрос OPTIONS. См. CORS.
405 Любой другой метод. Allow: GET, HEAD, OPTIONS.
503 История недоступна. Отправляется, пока не записана первая история, или когда сохранённую историю не удаётся прочитать дольше пяти минут. Содержит Retry-After: 30 и Cache-Control: no-store. Повторяйте запрос с нарастающими паузами и продолжайте показывать последнюю корректную историю, которая у вас есть.
404 Любой другой путь. Отвечает шлюз.
429 Вы превысили лимит из раздела Добросовестное использование. Приходит от CDN (ошибка Cloudflare 1015), а не от API. Длится около 60 секунд, Retry-After около 60.

Ветвитесь по коду состояния HTTP и никогда по тексту сообщения. Ошибка самого API имеет вид {"error": "<message>"}. Шлюзовый 404 и 429 от CDN используют другие тела: 429 — это страница Cloudflare в формате text/plain.

В браузере 429 прочитать нельзя. Страница 429 от CDN не содержит Access-Control-Allow-Origin, поэтому браузер блокирует её, и fetch завершается с TypeError, как при любом сетевом сбое или сбое CORS. Ваш код не видит ни кода состояния, ни Retry-After. Относитесь к любому сетевому сбою так же, как к 429 или 503: увеличивайте паузы и продолжайте показывать последнюю удачную историю с пометкой её generatedAt.

CORS

История публична и не содержит данных, зависящих от вызывающего, поэтому каждый ответ разрешает любой источник:

Заголовок Значение
Access-Control-Allow-Origin *
Access-Control-Expose-Headers ETag

Access-Control-Allow-Credentials нет. Обычный fetch с заголовком Accept — простой запрос, предварительный запрос не нужен. Скрипту, который сам задаёт If-None-Match, он нужен, и эндпоинт отвечает на собственный предварительный запрос OPTIONS кодом 204. Разрешённые методы и заголовки те же, что у живого эндпоинта.

Добросовестное использование

Действуют те же правила, что и для живого эндпоинта: ключа и SLA нет, соблюдайте Cache-Control и используйте ETag, серверы отправляют описательный User-Agent, а при более чем примерно 120 запросах в минуту с одного IP на этот маршрут приходит 429. При 429 и 5xx увеличивайте паузы между запросами.

Кроме того, поскольку эти данные меняются медленно, опрашивайте не чаще раза в минуту и предпочитайте загружать их, когда пользователь открывает экран, где они показываются. Если вы обслуживаете многих пользователей, загружайте их на своём сервере и кешируйте там.

Статус беты

Этот API находится в бета-версии, обещания те же, что у живого эндпоинта, и не более сильные.

  • Может измениться без предупреждения. Поля, символы, окна и форма ответа могут быть добавлены, переименованы, изменены по типу или удалены без срока уведомления, без обязательств по процедуре объявления устаревшим или вывода из эксплуатации. Срок хранения, длина интервала и окна доступности — сегодняшние значения.
  • schemaVersion может измениться. Если это не известное вам значение, считайте историю нечитаемой, пока не обновитесь. JSON Schema намеренно нестрогая и описывает сегодняшний ответ. Это не обещание.
  • Изменения фиксируются по мере возможности в журнале изменений ниже.

Как написать клиент, который это переживёт:

  • Игнорируйте неизвестные поля. На любом уровне: история, каждая строка и uptime.
  • Не полагайтесь на закрытые множества. Незнакомый символ интервала считайте - только для этого интервала. Игнорируйте строки с service или chainId, которые вам не нужны. Игнорируйте неизвестные ключи uptime.
  • Пусть сбоит одна строка, а не вся история. Одна нечитаемая строка, например без uptime или со строкой buckets неверной длины, не должна обнулять остальные.
  • Не зашивайте длину в код. Используйте from, to и bucketSeconds.

Журнал изменений

2026-10-07

Добавлено. GET /api/v1/railgun/liveness/history возвращает до 14 дней 5-минутных интервалов и доступность за 24 часа, 7 и 14 дней для каждой строки живого отчёта. schemaVersion 1, бета. История записывается начиная с 2026-10-07 01:55 UTC, заполнения задним числом нет.