01
Схема найденной карты
Свяжите карту матча с каталогом. Покажите схему и заполненные точки броска и приземления.
Выбрана карта матча — покажите игрокам схему, доступные гранаты и короткую инструкцию. API отдаёт опубликованные раскидки всех типов с медиа и точками на карте.
Для интеграции с помощью нейросетей рекомендуем English reference и текстовые llms.txt / llms-full.txt.
На вашем сайте

01
Свяжите карту матча с каталогом. Покажите схему и заполненные точки броска и приземления.
02
Покажите все опубликованные виды гранат, добавьте фильтры стороны и плента. Момент показа подсказки выбираете вы.
03
Подсветите, откуда и куда бросать. Покажите кадры по порядку или видео в собственном мини-просмотре.
Ваш сервер получает JSON, а вы выбираете оформление, взаимодействия и частоту показа. API передаёт данные и ссылки на медиа; готовый виджет или 3D-тренажёр на чужой сайт не устанавливает.
curl --fail-with-body 'https://warfacelineups.ru/api/v1/maps' \
--header "Authorization: Bearer $WARFACE_LINEUPS_TOKEN"Базовый URL: https://warfacelineups.ru/api/v1. Вызовы — по HTTPS. В примерах нет реального токена или реальных метрик каталога.
Все методы используют GET и заголовок Authorization. Ответы — JSON в UTF-8. Доступ только к опубликованному каталогу; создать, изменить или удалить раскидку через этот API нельзя.
Фильтры можно совмещать. Неизвестные, повторяющиеся и некорректные параметры отклоняются с 400 — опечатка не превращается в запрос всего каталога.
Справочники и одиночные объекты не принимают query-параметры. В /maps/{map}/lineups повторять map нельзя. Для первого сопоставления используйте aliases из /maps: у известных карт они включают игровые коды, например ptb_bridges_up. Сопоставляйте точное имя или alias после NFKC, обрезки пробелов и приведения к нижнему регистру; ё заменяется на е. Неоднозначность даёт 404. Сохраните полный id или стабильный slug вида map-<id>, поскольку название и псевдонимы могут меняться.
| Параметр | Значения | Поведение |
|---|---|---|
| map | id, slug или псевдоним карты | Только /lineups. Для вложенного маршрута карта уже задана в пути. |
| grenade | id, slug или однозначное название | Тип гранаты из /grenades. Без фильтра возвращаются все типы. |
| side | attack | defense | Атака или защита, включая общие раскидки для обеих сторон. Без фильтра — любые стороны. |
| plant | 1 | 2 | 10 | Плент; 10 — «без плента». Без фильтра — любые значения. |
| media | video | screenshots | map | Сохранённый формат: URL видео, кадры или кадр из тренажёра с записью броска. Без фильтра — все форматы. |
| page | Целое число 1–1000 | По умолчанию 1. |
| pageSize | Целое число 1–100 | По умолчанию 50. |
Фильтр media проверяет сохранённые поля материала; он не гарантирует воспроизведение старого видео или корректность старой записи броска. screenshots включает любой материал с кадрами, в том числе кадр из тренажёра. Для показа проверяйте нормализованные media.kind, media.video и media.screenshots в ответе.
{
"data": [],
"pagination": {
"page": 1, "pageSize": 50, "total": 0,
"pages": 0, "hasNextPage": false
}
}Одиночный объект и справочники также находятся в поле data. Чтобы получить все гранаты карты, загружайте страницы, пока pagination.hasNextPage не станет false. Не предполагайте, что первая страница содержит всю карту. При выходе за последнюю страницу data пуст; при изменении каталога во время обхода повторяющиеся id можно объединить.
Все перечисленные поля присутствуют в ответе. null указан явно; массивы могут быть пустыми. Ниже описание формы JSON, а не SDK. Объекты map и grenade внутри раскидки содержат только id, slug и name; схему получите отдельным запросом карты.
type Reference = {id: string; slug: string; name: string};
type Point = {x: number; y: number};
type BombSite = Point & {id: string; kind: 'site'; label: string};
type Scheme = {
imageUrl: string; width: number; height: number;
coordinateSystem: 'normalized-top-left'; bombSites: BombSite[];
};
type MapData = Reference & {
aliases: string[]; canonicalUrl: string; lineupCount: number;
scheme: Scheme | null;
};
type Grenade = Reference & {lineupCount: number};
type Screenshot = {index: number; url: string; thumbnailUrl: string};
type Video = {
provider: 'youtube'; id: string; watchUrl: string; embedUrl: string;
};
type Lineup = {
id: string; title: string; description: string; canonicalUrl: string;
map: Reference; grenade: Reference;
side: 'attack' | 'defense' | 'both' | null;
typeSide: number; typePlant: number; typeClick: string;
media: {
kind: 'video' | 'screenshots' | 'map' | 'unavailable';
previewUrl: string | null; screenshots: Screenshot[];
screenshotCount: number; screenshotsTruncated: boolean;
video: Video | null;
};
placement: {
throwPoint: Point | null; landPoint: Point | null;
coordinateSystem: 'normalized-top-left';
connection: 'straight-line-guide' | null;
};
createdAt: string; updatedAt: string;
};
type Pagination = {
page: number; pageSize: number; total: number;
pages: number; hasNextPage: boolean;
};Для проверки ответов используйте OpenAPI; со временем могут добавляться новые поля. Текст инструкции — пользовательская строка, а не доверенный HTML.
Карта содержит scheme или null. Схема включает imageUrl, исходные width и height, coordinateSystem и bombSites. Метки спавна и подписи позиций не публикуются. Массив aliases помогает связать игровой код карты с каталогом; сохраните найденный id у себя, чтобы повторно не сопоставлять название матча.
function pointInImage(point, scheme, containerWidth, containerHeight) {
if (!point || !scheme) return null;
const scale = Math.min(
containerWidth / scheme.width, containerHeight / scheme.height
);
const width = scheme.width * scale;
const height = scheme.height * scale;
return {
left: (containerWidth - width) / 2 + point.x * width,
top: (containerHeight - height) / 2 + point.y * height,
};
}Пример возвращает смещение от левого верхнего угла контейнера. Двигайте и масштабируйте изображение вместе со слоем маркеров; не обрезайте и не поворачивайте подложку. Если scheme или точки отсутствуют, сохраните материал в списке с медиа и ссылкой. null нельзя заменять координатой 0.
После выбора карты загрузите её схему и все страницы раскидок. При наведении или фокусе выделяйте пару точек; по нажатию закрепляйте карточку со скриншотами и кнопкой видео. На телефоне используйте нажатие. Частоту и момент показа подсказок определяет ваше приложение.
Браузер обращается к вашему backend, а backend — к Warface Lineups. CORS для прямого подключения стороннего браузера не включён. Не передавайте токен в query string, localStorage, NEXT_PUBLIC-переменные, клиентский JavaScript или embed-код.
// Backend only. Save as an ES module (.mjs); requires Node.js 22+.
// Keep WARFACE_LINEUPS_TOKEN in private server configuration.
const tokenName = 'WARFACE_LINEUPS_TOKEN';
const token = process.env[tokenName];
if (!token) throw new Error('Missing API token');
const base = "https://warfacelineups.ru/api/v1";
async function api(path) {
const response = await fetch(base + path, {
headers: {Authorization: 'Bearer ' + token},
signal: AbortSignal.timeout(8000), cache: 'no-store',
});
if (!response.ok) {
const error = new Error('Lineups API HTTP ' + response.status);
error.status = response.status;
error.retryAfter = response.headers.get('Retry-After');
throw error; // Honor Retry-After on 429; never log headers.
}
return response.json();
}
export async function getMapLineupBatch(mapReference, page = 1) {
if (typeof mapReference !== 'string' || !mapReference.trim() || mapReference.length > 100) {
throw new Error('Invalid map reference');
}
if (!Number.isInteger(page) || page < 1 || page > 1000) {
throw new Error('Invalid continuation page');
}
const {data: map} = await api('/maps/' + encodeURIComponent(mapReference));
if (!map || typeof map.id !== 'string') throw new Error('Invalid map response');
const byId = new Map();
const stopPage = Math.min(1000, page + 4); // At most five pages per batch.
for (; page <= stopPage; page++) {
const result = await api('/maps/' + encodeURIComponent(map.id) +
'/lineups?pageSize=100&page=' + page);
if (!Array.isArray(result.data) || result.pagination?.page !== page ||
typeof result.pagination?.hasNextPage !== 'boolean') {
throw new Error('Invalid lineup response');
}
for (const item of result.data) byId.set(item.id, item);
if (!result.pagination.hasNextPage) {
return {map, lineups: [...byId.values()], complete: true,
nextPage: null, paginationLimitReached: false};
}
}
return {map, lineups: [...byId.values()], complete: false,
nextPage: stopPage < 1000 ? stopPage + 1 : null,
paginationLimitReached: stopPage === 1000};
}
// Start with the stable map id selected from GET /maps.
// Continue with getMapLineupBatch(mapId, previous.nextPage).
// Merge batches by lineup.id. Send public data, never the token, to the browser.Сохраните код выше в lineups-api.mjs на своём сервере с Node.js 22+ и задайте закрытую переменную WARFACE_LINEUPS_TOKEN. Затем сохраните следующий код рядом в read-lineups.mjs и запустите его, передав реальный id из /maps вместо MAP_ID_FROM_CATALOGUE. Результат — JSON, а не готовая веб-страница.
// Save as read-lineups.mjs beside lineups-api.mjs.
const modulePath = './lineups-api.mjs';
const {getMapLineupBatch} = await import(modulePath);
const mapId = process.argv[2]; // Full id returned by GET /maps.
const page = Number(process.argv[3] ?? '1');
if (!mapId) throw new Error('Pass a catalogue map id as the first argument');
const batch = await getMapLineupBatch(mapId, page);
console.log(JSON.stringify(batch, null, 2)); // Public data only, no token.
// Run: node read-lineups.mjs MAP_ID_FROM_CATALOGUE
// If nextPage is not null: run again with that page as the second argument.
// In a web app, return batch as JSON from your own backend route instead.
// The frontend reads map.scheme and lineups; it never receives token headers.Передайте найденный id карты в getMapLineupBatch. Один вызов загружает до 5 страниц по 100 раскидок и схему. complete = false означает, что данные ещё неполные: продолжите с nextPage, объединяя результаты по id и соблюдая лимит запросов. complete = true завершает обход. Если paginationLimitReached = true, достигнута страница 1000: сохраните данные, обозначьте неполноту и сузьте фильтры для дальнейшего поиска. Исключение не превращается в пустой каталог; пример не делает автоматических повторов. Для проверки всей формы ответа используйте OpenAPI.
В веб-приложении вызовите эту функцию из своего серверного обработчика и верните браузеру только map, lineups и состояние продолжения. На клиенте отрисуйте scheme.imageUrl, отметьте доступные точки и используйте ссылки media для просмотра. Токен и заголовок Authorization остаются на вашем backend. Храните nextPage на своей стороне между пакетами; API не создаёт отдельную сессию обхода.
Объединяйте одновременные запросы одной карты и задайте короткий срок локального кеша, например 60 секунд. Он определяет давность данных в вашем приложении, а не гарантирует публикацию навсегда. При 404 удаляйте материал из текущей выдачи; при временном сбое оставляйте последние данные с понятным состоянием. Медиа загружайте по мере просмотра. Ссылку «Открыть в Warface Lineups» берите из canonicalUrl.
120 запросов на токен за фиксированную календарную минуту, включая запросы, которые закончились ошибкой после авторизации. На границе минуты начинается новое окно. X-RateLimit-Limit сообщает лимит, на 429 Retry-After задаёт ожидание в секундах. Ответы API имеют Cache-Control: private, no-store: не помещайте их вместе с Authorization в публичный CDN-кеш.
{
"error": {
"code": "UNAUTHORIZED",
"message": "A valid Bearer token is required."
}
}До 5 активных токенов на аккаунт. Срок каждого — 365 дней; до 10 попыток создания за час. Полный ключ показывается один раз. Потерянный ключ отзовите и создайте новый. Отзыв, смена или сброс пароля и завершение других сеансов аккаунта прекращают действие ранее созданных токенов. Токен предоставляет только чтение публичного каталога и не даёт доступ к профилю, модерации или неопубликованным материалам.
При обработке ошибок ориентируйтесь на HTTP status и error.code; текст message предназначен для пояснения и может меняться. Даты createdAt и updatedAt — ISO 8601 в UTC. Не считайте число раскидок постоянным и сохраняйте совместимость с новыми полями ответа. Несовместимые изменения получат новую версию API.