# Warface Lineups Public API v1: English integration reference This document describes the API implemented by this deployment. Human documentation: `/developers/en` and `/developers`. Machine schema: `/openapi.json`. Concise documentation index: `/llms.txt`. ## Scope and availability The base path is `/api/v1` on the deployment's HTTPS origin. The API is read-only and exposes published Warface lineups of every grenade category in the project's catalogue. A token never grants access to pending or rejected materials, account details, private collections, moderation data or mutation endpoints. A token owner's staff role does not expand public API access. API documentation is public and unauthenticated. All six API endpoints require Bearer authentication. Integrations receive catalogue data through this token-authenticated API. Handle HTTP 503 as unavailable. The presence of documentation, a schema or an example does not guarantee live API availability or particular maps, counts or media. Obtain actual catalogue values from API responses; do not invent observations from examples. Use the API from your application's backend. The API does not opt into cross-origin browser access through CORS. Do not put a personal token in browser JavaScript, a mobile client bundle, public repository, URL query parameter, local storage, analytics, logs or a client-visible environment variable. A frontend should request the necessary public data from its own backend; that backend supplies the upstream Bearer header. Never forward the token to an end user's browser. ## Personal token lifecycle Sign in to the site's account settings at `/profile/settings#api` and create a token with a descriptive name of 2 through 80 characters. Names containing control characters or angle brackets are rejected. The full token is returned only when created; save it immediately to private server configuration. The account list shows a short display prefix; the full token cannot be recovered from that list. An account may have at most 5 active tokens. A token expires 365 days after creation. Token creation is limited to 10 attempts per fixed hour per account. An owner can revoke their own token in settings. Repeating a revocation is safe. Account deletion and account security/session changes can invalidate tokens, including password changes, password resets and signing out other sessions. A normal sign-out from a browser is not a token revocation action. Replace an expired, revoked or invalidated token before retrying requests that return 401. Supply exactly one header in this form, substituting your private value at runtime: ```http Authorization: Bearer ``` `` is a placeholder, not a usable key. Tokens are accepted only in the Authorization header. API token management is an authenticated account feature, not an endpoint in API v1. ## Endpoint inventory and envelopes Only GET is provided. All paths below are relative to `/api/v1`. | Path | Success body | Query support | | --- | --- | --- | | `/maps` | `{ "data": Map[] }` | None | | `/maps/{map}` | `{ "data": Map }` | None | | `/maps/{map}/lineups` | `{ "data": Lineup[], "pagination": Pagination }` | grenade, side, plant, media, page, pageSize | | `/grenades` | `{ "data": Grenade[] }` | None | | `/lineups` | `{ "data": Lineup[], "pagination": Pagination }` | map, grenade, side, plant, media, page, pageSize | | `/lineups/{id}` | `{ "data": Lineup }` | None | Directories are not paginated and may include entries with zero published lineups. Map and grenade directories are ordered by name, then id. Lineup pages are ordered by creation time descending, then id descending. The id tie-breaker makes ordering deterministic; pages are not a point-in-time snapshot. If the catalogue changes during pagination, deduplicate results by lineup `id` and refresh when needed. `{map}` accepts a full map id, a stable API slug, the current website map slug, or an unambiguous alias. `{id}` on `/lineups/{id}` must be the full 24-character hexadecimal lineup id, not the human website URL slug. Use `encodeURIComponent` for path values and URLSearchParams for query values. Unknown, invalid or ambiguous resources return 404 rather than a guessed match. All query parameter names and enumerated values are case-sensitive. Duplicate parameters, unknown parameters, unsupported values and malformed bounds return 400. Do not add `token`, `access_token`, arbitrary cache-busting parameters or other undeclared query fields. Endpoints with no query support reject every query parameter. ## Match-map resolution 1. Fetch `/maps` on your backend. 2. Match the selected match map against each entry's `id`, `slug`, `name` or `aliases`. 3. Use a deterministic normalization for textual matching: Unicode NFKC, trim, lowercase, and replace Russian `ё` with `е`. 4. Persist an explicit mapping from your application's map key to the returned full `id` or API `slug`. 5. Fetch `/maps/{map}` and `/maps/{map}/lineups` using that stored mapping. The API map slug is `map-` followed by the full lowercase map id. It remains stable when the display name changes. Names, human website paths and aliases are for display or initial matching and can change. Never infer identity from a display-name substring. If more than one map matches an alias, require an explicit mapping instead of selecting the first. Current aliases include forms such as `mosty`, `bridges`, `Мосты` and the verified native game key `ptb_bridges_up` for a map named Мосты, when that map exists. Native game-key coverage varies by map: use keys actually returned in `aliases`; do not synthesize unknown keys or assume every legacy version is interchangeable. An alias example is not proof that the deployment contains the map. Map and grenade ids are stable identifiers. Grenade slugs derive from the current name and an id suffix; persist a grenade's full `id` if your integration needs identity across renames. Do not hard-code a smoke-only category list. Fetch `/grenades` or read each lineup's `grenade` reference so additional categories remain usable. ## Lineup filters and pagination All filters are optional. Omit a filter to include all matching published values. Filters combine with AND. | Parameter | Accepted values and semantics | | --- | --- | | `map` | Map id, API slug, current website slug or unambiguous alias. Only allowed on `/lineups`; `/maps/{map}/lineups` rejects a second `map` query parameter, even if equivalent. | | `grenade` | Full grenade id, current grenade slug, name or transliterated name. Ambiguous or unknown references return 404. | | `side` | `attack` or `defense`. `attack` includes stored side codes 1 and 3; `defense` includes 2 and 3. Code 3 means both sides. | | `plant` | Integer `1`, `2` or `10`; 10 means no specific bomb site. This selects exactly that code; 10 is not a wildcard. Omit to include all plant values. | | `media` | `video`, `screenshots` or `map`; these inspect stored material fields, as detailed below. | | `page` | Integer 1 through 1000; default 1. | | `pageSize` | Integer 1 through 100; default 50. | Integer values use plain positive decimal notation without signs, decimal points, exponent notation or leading zeroes. Map and grenade query strings must be nonempty after trimming, at most 100 characters, and contain no control characters. Media filter semantics are deliberately about stored material, not a promise that a legacy URL or recipe is playable. `media=video` selects a declared non-null video URL. `media=screenshots` selects a positive stored screenshot count, including one-image map materials and any video material that also has screenshots. `media=map` selects a stored screenshot count of 1 and a non-null stored trainer recipe. These groups may overlap. The output separately validates supported videos and recipes: inspect `media.video`, `media.kind`, `media.screenshots` and nullable values before rendering. A malformed historical video URL can pass the stored-video filter while returning `media.video: null`. The default unfiltered request includes all published formats. `Pagination` has these exact fields: ```ts type Pagination = { page: number; pageSize: number; total: number; pages: number; hasNextPage: boolean; }; ``` `total` counts all published records matching the filters. `pages` is the ceiling of total/pageSize and is 0 when no records match. The requested page is not silently clamped. A page beyond the last existing page returns an empty `data` array and `hasNextPage: false`. Request subsequent pages while `hasNextPage` is true, within the documented 1000-page bound and rate limit. If an application imposes a smaller work budget, expose an incomplete state and continuation page instead of claiming that the first batch contains every available grenade. ## Exact data fields The following TypeScript notation describes JSON fields, not a required JavaScript SDK. Null is an explicit value, not an omitted coordinate. Date strings are ISO 8601 UTC timestamps. ```ts type Reference = { id: string; slug: string; name: string; }; type Point = { x: number; y: number }; type BombSite = { id: string; kind: "site"; label: string; x: number; y: number; }; type Scheme = { imageUrl: string; width: number; height: number; coordinateSystem: "normalized-top-left"; bombSites: BombSite[]; }; type Map = 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 Media = { kind: "video" | "screenshots" | "map" | "unavailable"; previewUrl: string | null; screenshots: Screenshot[]; screenshotCount: number; screenshotsTruncated: boolean; video: Video | null; }; type Placement = { throwPoint: Point | null; landPoint: Point | null; coordinateSystem: "normalized-top-left"; connection: "straight-line-guide" | null; }; 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: Media; placement: Placement; createdAt: string; updatedAt: string; }; ``` Map.scheme is null when the map has no configured scheme. An empty `bombSites` array means no public bomb-site markers were recorded. Public schemes do not expose legacy callouts or spawn markers. Lineup.map and Lineup.grenade are compact references: obtain the actual image and bomb sites from `/maps/{map}` rather than expecting a nested scheme in every lineup. `side` maps stored codes 1 to attack, 2 to defense, 3 to both, and other historical codes to null. `typeSide` preserves the stored integer. `typePlant` preserves the stored plant code; currently 1 and 2 are specific sites and 10 is no specific site. `typeClick` preserves the stored string, currently `lc` for left mouse button and `rc` for right mouse button. There is no click-type query filter in API v1; filter this field locally if needed. Treat future or unknown values as unknown instead of inventing a meaning. Treat titles, descriptions, names and labels as plain text, never trusted HTML. `canonicalUrl` is the public website destination for that map or lineup. Use it for attribution and a full-instruction link. Do not build human website URLs by concatenating a title or API map slug. ## Media and miniature previews Screenshots remain in the author's stored order with zero-based `index`. The first screenshot is the preview; do not automatically label it as the throwing position or assume fixed semantic roles for later frames. Use `thumbnailUrl` for a miniature preview and `url` for a larger image. Read the returned array rather than assuming every instruction has the same number of images. The payload includes at most 12 screenshot entries per lineup. `screenshotCount` is the positive stored integer count, normalized to 0 for invalid counts. `screenshotsTruncated` is true when the reported count exceeds the returned array bound. Do not present a truncated array as every source image. Offer `canonicalUrl` to open the website instruction. Returned image URLs are references, not a guarantee that every image is currently reachable; show a fallback on media-load failure. YouTube video URLs are parsed and validated; arbitrary stored URLs are not returned as playable embeds. If available, `video` contains the provider, 11-character YouTube id, canonical HTTPS watch URL and embed URL. It is otherwise null. These fields describe the supported video reference; they do not verify that YouTube currently serves the video in every region. Load an iframe only after a user chooses to play. A static hover/focus preview can use the returned `previewUrl`; do not reconstruct thumbnail or embed URLs yourself. `media.kind` is the primary normalized display format: a validated supported video wins, otherwise one screenshot with a validated trainer recipe is `map`, otherwise available screenshot entries are `screenshots`, otherwise `unavailable`. Stored trainer recipes and simulated replay data are not exposed by API v1. A `map` media kind does not promise sampled trajectory data. For the found-match UI: show a compact list or markers for every available category; hover or keyboard focus highlights a lineup's from/to relationship and static preview; a click or touch selection keeps the preview open. Offer numbered screenshots or explicit video playback. Keep a link to `canonicalUrl`. Let the integrating application's owner choose how often the feature is offered; API v1 does not schedule prompts or force the preview open. ## Coordinate system and image bounds Every valid point has finite `x` and `y` values in the inclusive range 0 through 1. The origin is the top-left corner of the scheme image; x increases to the right and y downwards. Coordinates refer to the entire image bounds. They are not world-space coordinates, metres, distances from a bomb site or coordinates of the outer card containing the image. Place markers inside the actual displayed image rectangle. If an image is letterboxed using object-fit contain, compute its fitted rectangle first. Given a container of width Cw and height Ch and the scheme's width W and height H: ```text scale = min(Cw / W, Ch / H) displayWidth = W * scale displayHeight = H * scale offsetX = (Cw - displayWidth) / 2 offsetY = (Ch - displayHeight) / 2 markerX = offsetX + point.x * displayWidth markerY = offsetY + point.y * displayHeight ``` Apply pan and zoom to the image and marker layer together. Do not crop, rotate, flip or reframe the image independently of the points. Preserve the returned image's aspect ratio and framing. Normalization makes points resolution-independent, not immune to changed geometry. `throwPoint` is the throwing position and `landPoint` is the recorded landing position. Each can independently be null for an older or unplaced lineup. Preserve such lineups in a list and show their available media; missing points are not a reason to omit the grenade from the integration. Never replace null with {x:0,y:0}. Draw a relation only when both points exist and a usable map scheme is available. When both points exist, `connection` is `straight-line-guide`. This explicitly describes an illustrative from/to connection, not the physically sampled path of a grenade. API v1 provides no bounce positions, height, flight timing, intermediate samples or guaranteed landing physics. Do not present a decorative curve or a straight connector as a simulation or infer intermediate collision points from the two endpoints. ## Limits, errors and retry handling Each token has a quota of 120 authorized requests per fixed UTC minute window. A new minute opens a new window; this is not a sliding limit and can allow requests on both sides of a minute boundary. The quota is charged before query validation and catalogue loading, so an authorized request can consume a slot even if it later returns 400, 404 or 503. Directory requests, detail requests and pagination all consume the same token quota. Token quota is independent of application rendering frequency. Reuse retrieved map metadata within your own permitted backend policy; never make one upstream API call per visible marker or per hover event. Normal API responses use `Cache-Control: private, no-store`, `X-Content-Type-Options: nosniff` and `X-RateLimit-Limit: 120`. There are no promised Remaining or Reset headers. HTTP 401 includes `WWW-Authenticate: Bearer realm="Warface Lineups API"`. HTTP 429 includes integer `Retry-After` seconds. Respect it and apply a bounded retry/work policy. Do not immediately repeat a failed request in an unlimited loop. Error bodies use this envelope: ```ts type ErrorResponse = { error: { code: "INVALID_QUERY" | "UNAUTHORIZED" | "NOT_FOUND" | "RATE_LIMITED" | "API_UNAVAILABLE"; message: string; }; }; ``` | HTTP | Code | Meaning and handling | | --- | --- | --- | | 400 | INVALID_QUERY | Unknown/duplicate query fields or invalid filter/pagination. Fix the request. | | 401 | UNAUTHORIZED | Missing, malformed, unknown, expired, revoked or invalidated token. Ask the owner to check or replace it. | | 404 | NOT_FOUND | Unknown/ambiguous map or grenade, invalid lineup id, missing lineup or a lineup not publicly published. Do not infer private status. | | 429 | RATE_LIMITED | Per-token quota reached. Wait at least Retry-After before retrying. | | 503 | API_UNAVAILABLE | API temporarily unavailable. Show an unavailable/retry state. | Messages are human-readable and can change. Branch on status and `error.code`, not exact message wording. Do not display raw provider errors. A failed request is not an empty catalogue and must not overwrite a previously known valid dataset as though no lineups exist. If you show earlier data after an upstream failure, mark it as stale and retain its retrieval time. ## Bounded backend pagination example The following Node/JavaScript example is intended for a backend only. Set WARFACE_LINEUPS_ORIGIN to the trusted deployment's HTTPS origin (for example, the origin serving this documentation) and WARFACE_LINEUPS_TOKEN to the personal token using your application's private server configuration. Do not accept the API origin from an end user's request. These variable names belong to the integrating application. Do not expose request headers containing the token, or the token itself, to the browser. This function reads a bounded batch of up to 5 pages of 100 lineups and explicitly returns a continuation. It never silently claims a partial batch is complete. Call again with `nextPage` when it is non-null and your server's work and rate budgets allow. A first batch may need fewer requests. If `paginationLimitReached` is true, the set remains incomplete and no further page is accessible within the 1000-page bound. This example has no automatic retries; the caller handles rate and availability errors. ```js export async function getLineupBatch(mapReference, startPage = 1) { const configuredOrigin = process.env.WARFACE_LINEUPS_ORIGIN; const token = process.env.WARFACE_LINEUPS_TOKEN; if (!configuredOrigin || !token) { throw new Error('Configure a trusted HTTPS API origin and private token'); } const origin = new URL(configuredOrigin); if (origin.protocol !== 'https:' || origin.username || origin.password || origin.pathname !== '/' || origin.search || origin.hash) { throw new Error('API origin must be HTTPS without credentials, path or query'); } if (typeof mapReference !== 'string' || !mapReference.trim() || mapReference === '.' || mapReference === '..') { throw new Error('Use a map reference returned by the API'); } if (!Number.isInteger(startPage) || startPage < 1 || startPage > 1000) { throw new Error('Invalid continuation page'); } const byId = new Map(); const stopPage = Math.min(1000, startPage + 4); for (let page = startPage; page <= stopPage; page++) { const url = new URL( `/api/v1/maps/${encodeURIComponent(mapReference)}/lineups`, origin.origin ); url.searchParams.set('page', String(page)); url.searchParams.set('pageSize', '100'); const response = await fetch(url, { headers: { Authorization: `Bearer ${token}` }, cache: 'no-store', signal: AbortSignal.timeout(10000), }); if (!response.ok) { const retryAfter = response.headers.get('Retry-After'); const error = new Error(`Lineup API request failed: ${response.status}`); error.status = response.status; error.retryAfterSeconds = retryAfter && /^\d+$/.test(retryAfter) ? Number(retryAfter) : null; throw error; } const body = await response.json(); if (!Array.isArray(body.data) || body.pagination?.page !== page || typeof body.pagination?.hasNextPage !== 'boolean') { throw new Error('Unexpected Lineup API response'); } for (const lineup of body.data) byId.set(lineup.id, lineup); if (!body.pagination.hasNextPage) { return { items: [...byId.values()], complete: true, nextPage: null, paginationLimitReached: false, }; } } return { items: [...byId.values()], complete: false, nextPage: stopPage < 1000 ? stopPage + 1 : null, paginationLimitReached: stopPage === 1000, }; } ``` For production, validate the complete response against `/openapi.json`, merge continuation batches by lineup id, use a shared backend request budget, and report whether the set is complete. Retrieve the map scheme separately. Keep missing-coordinate materials in the list. Avoid long-running upstream work directly on every player's match-page request; choose a server refresh strategy suited to your traffic and the API quota. Handle re-publication, deletion and changed metadata on later refreshes rather than treating ids or media URLs as a permanent entitlement.