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