API Reference
The WardenCore public REST API — authentication, errors, and conventions.
The WardenCore API lets your own tools, bots, and scripts drive a server programmatically — read players, run commands, manage the economy, and more.
Base URL
All requests go to https://api.wardencore.ru/v1. Every request must be made over HTTPS.
Premium feature
The API is available on servers with an active premium plan. Creating a key
or calling /v1 on a non-premium server returns 403 premium_required, and if
a server's premium lapses its existing keys stop working until it's renewed.
Authentication
The API authenticates with a per-server API key. Create one in the panel under Settings → API (see API Keys) — the key is shown once, so copy it immediately.
Send it as a bearer token:
Authorization: Bearer wc_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxA key is bound to exactly one server and carries a fixed set of scopes (permissions). A request that needs a scope the key doesn't have returns 403 insufficient_scope.
A key can also be restricted to specific source IPs or CIDR ranges (IPv4/IPv6) in the panel. A request from an IP outside a restricted key's allowlist returns 403 ip_not_allowed.
Response format & errors
Successful responses are JSON with snake_case fields. Errors always use one stable envelope:
{
"error": {
"code": "insufficient_scope",
"message": "The API key lacks the required scope: players:write.",
"details": { "missing_scope": "players:write" },
"request_id": "req_9f2c…"
}
}Always branch on the machine-readable code, never the human-readable message. Include the request_id when contacting support.
Response shape
- A single resource is returned as a bare JSON object (no wrapper).
- A collection is returned as
{ "data": [...], "has_more": boolean, "next_cursor": string | null }(see Pagination). - A mutation returns the affected resource (bare), or
204 No Contentwhen there is nothing to return; a freshly created resource comes back as201 Created. - Fields are never omitted. An absent value is
null, not a missing key — so you can parse a stable shape. New fields may be added withinv1; ignore ones you don't recognize.
Number handling
Integer fields are JSON numbers. A value that can exceed 2⁵³−1 (large identifiers and lifetime counters) is sent as a decimal string instead, documented per field — treat those as opaque. Coordinates and other fractional values are ordinary JSON numbers.
Request limits
Request bodies must be sent as Content-Type: application/json and are limited to 1 MB. A larger body returns 413 payload_too_large; any other content type returns 415 unsupported_media_type.
| Code | Status | Meaning |
|---|---|---|
unauthorized | 401 | Missing or invalid API key. |
premium_required | 403 | The server does not have an active premium plan. |
insufficient_scope | 403 | The key lacks a required scope. |
ip_not_allowed | 403 | The key is restricted to other source IPs. |
validation_error | 400 | The request body or query failed validation. |
not_found | 404 | The resource (or route) does not exist. |
rate_limited | 429 | Too many requests — see rate limits. |
idempotency_conflict | 409 | An Idempotency-Key was reused with a different body. |
conflict | 409 | The request conflicts with the current state of the resource. |
precondition_failed | 412 | An If-Match precondition failed — the resource changed. |
payload_too_large | 413 | The request body exceeds the size limit. |
unsupported_media_type | 415 | The request body must be sent as application/json. |
server_unavailable | 503 | The game server is offline or unreachable. Retry later. |
gateway_timeout | 504 | The game server did not respond in time. |
Rate limits
Limits are applied per API key. Every response carries the IETF draft headers:
RateLimit-Limit: 120
RateLimit-Remaining: 118
RateLimit-Reset: 42
RateLimit-Policy: 120;w=60When you exceed the limit you get 429 rate_limited with a Retry-After header (seconds), in the standard error envelope.
Two layers of 429
The per-key limit above is enforced by the application and always returns the
JSON rate_limited envelope plus the RateLimit-* and Retry-After headers.
A separate coarse per-IP limit at the edge (nginx) guards against floods
before the request reaches the application — when it rejects you, the 429
may be a bare response with no JSON envelope and no Retry-After. Treat
any 429 as "back off and retry later"; branch on the status code, and only
rely on the envelope/headers when they're present.
Pagination
List endpoints are cursor-paginated. Responses carry has_more and next_cursor; pass the opaque next_cursor back as the cursor query parameter to fetch the next page. Do not construct cursors yourself — a cursor is an opaque token that encodes only your pagination position, so tampering with it can only move you within your own results, never widen what you can access.
Idempotency
Send an Idempotency-Key header on POST/PUT requests to make retries safe. Reusing the same key replays the original response instead of executing the action twice; reusing it with a different body returns 409 idempotency_conflict.
Idempotency-Key: 3f9a1c7e-…Caching
Read endpoints that are safe to cache return a strong ETag and a Cache-Control header (e.g. private, max-age=30). To make a conditional request, send the ETag back as If-None-Match on the next call — if nothing changed, the API replies 304 Not Modified with an empty body, so you reuse your cached copy and save bandwidth:
GET /v1/… HTTP/1.1
Authorization: Bearer wc_live_…
If-None-Match: "9f2c8ab1…"Cache-Control: privatemeans the response is scoped to your key — never store it in a shared/CDN cache. Astale-while-revalidatedirective, when present, tells your cache how long it may serve the stale copy while it refreshes in the background.- A weak validator (
If-None-Match: W/"…") is accepted (RFC 9110 weak comparison). - Live-status endpoints (e.g.
GET /v1/server) are intentionally not cached — they always reflect the current state.
Concurrency control
To avoid lost updates when several clients write the same resource, use optimistic concurrency. A GET returns the resource's current ETag; send it back as If-Match on your next write:
PUT /v1/… HTTP/1.1
Authorization: Bearer wc_live_…
If-Match: "9f2c8ab1…"If the resource changed since that ETag, the write is rejected with 412 precondition_failed and nothing is modified — re-fetch, reconcile, and retry. If-Match uses strong comparison (RFC 9110), so a weak validator never satisfies it. Omitting If-Match skips the check (last-write-wins).
Versioning & deprecation
The API is versioned in the path (/v1). Breaking changes ship under a new version; within a version we only ever add fields, so parse defensively and ignore unknown ones. Every response carries an API-Version header.
When an endpoint or a whole version is deprecated, its responses carry a Deprecation header — an RFC 9745 date in Structured-Field form (@<unix-seconds>) marking when it was deprecated. Once a removal date is set they also carry a Sunset header (an HTTP-date, RFC 8594). Watch for both and migrate before the sunset date.
Server availability
The API proxies to the WardenCore mod on your server over a live connection. If the server is offline, requests that need it return 503 server_unavailable with a Retry-After header — retry once the server is back online.
The API is off until an operator enables it
The public API ships disabled. Until the server operator turns it on, every /v1 request answers
404 not_found, the same for every endpoint and every key.
That looks like an ordinary miss, so check the scope: a disabled API 404s paths you know exist, while a real not-found — an unknown player ref, say — comes from an endpoint that answers other requests normally.
Endpoints
The full endpoint reference is published per release as we roll endpoints out. This page documents the conventions every endpoint follows.